| /* |
| * QEMU network listener |
| * |
| * Copyright (c) 2016-2017 Red Hat, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, see <http://www.gnu.org/licenses/>. |
| * |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "io/net-listener.h" |
| #include "io/dns-resolver.h" |
| #include "qapi/error.h" |
| |
| QIONetListener *qio_net_listener_new(void) |
| { |
| QIONetListener *ret; |
| |
| ret = QIO_NET_LISTENER(object_new(TYPE_QIO_NET_LISTENER)); |
| |
| return ret; |
| } |
| |
| void qio_net_listener_set_name(QIONetListener *listener, |
| const char *name) |
| { |
| g_free(listener->name); |
| listener->name = g_strdup(name); |
| } |
| |
| |
| static gboolean qio_net_listener_channel_func(QIOChannel *ioc, |
| GIOCondition condition, |
| gpointer opaque) |
| { |
| QIONetListener *listener = QIO_NET_LISTENER(opaque); |
| QIOChannelSocket *sioc; |
| |
| sioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc), |
| NULL); |
| if (!sioc) { |
| return TRUE; |
| } |
| |
| if (listener->io_func) { |
| listener->io_func(listener, sioc, listener->io_data); |
| } |
| |
| object_unref(OBJECT(sioc)); |
| |
| return TRUE; |
| } |
| |
| |
| int qio_net_listener_open_sync(QIONetListener *listener, |
| SocketAddress *addr, |
| Error **errp) |
| { |
| QIODNSResolver *resolver = qio_dns_resolver_get_instance(); |
| SocketAddress **resaddrs; |
| size_t nresaddrs; |
| size_t i; |
| Error *err = NULL; |
| bool success = false; |
| |
| if (qio_dns_resolver_lookup_sync(resolver, |
| addr, |
| &nresaddrs, |
| &resaddrs, |
| errp) < 0) { |
| return -1; |
| } |
| |
| for (i = 0; i < nresaddrs; i++) { |
| QIOChannelSocket *sioc = qio_channel_socket_new(); |
| |
| if (qio_channel_socket_listen_sync(sioc, resaddrs[i], |
| err ? NULL : &err) == 0) { |
| success = true; |
| |
| qio_net_listener_add(listener, sioc); |
| } |
| |
| qapi_free_SocketAddress(resaddrs[i]); |
| object_unref(OBJECT(sioc)); |
| } |
| g_free(resaddrs); |
| |
| if (success) { |
| error_free(err); |
| return 0; |
| } else { |
| error_propagate(errp, err); |
| return -1; |
| } |
| } |
| |
| |
| void qio_net_listener_add(QIONetListener *listener, |
| QIOChannelSocket *sioc) |
| { |
| if (listener->name) { |
| char *name = g_strdup_printf("%s-listen", listener->name); |
| qio_channel_set_name(QIO_CHANNEL(sioc), name); |
| g_free(name); |
| } |
| |
| listener->sioc = g_renew(QIOChannelSocket *, listener->sioc, |
| listener->nsioc + 1); |
| listener->io_tag = g_renew(gulong, listener->io_tag, listener->nsioc + 1); |
| listener->sioc[listener->nsioc] = sioc; |
| listener->io_tag[listener->nsioc] = 0; |
| |
| object_ref(OBJECT(sioc)); |
| listener->connected = true; |
| |
| if (listener->io_func != NULL) { |
| object_ref(OBJECT(listener)); |
| listener->io_tag[listener->nsioc] = qio_channel_add_watch( |
| QIO_CHANNEL(listener->sioc[listener->nsioc]), G_IO_IN, |
| qio_net_listener_channel_func, |
| listener, (GDestroyNotify)object_unref); |
| } |
| |
| listener->nsioc++; |
| } |
| |
| |
| void qio_net_listener_set_client_func(QIONetListener *listener, |
| QIONetListenerClientFunc func, |
| gpointer data, |
| GDestroyNotify notify) |
| { |
| size_t i; |
| |
| if (listener->io_notify) { |
| listener->io_notify(listener->io_data); |
| } |
| listener->io_func = func; |
| listener->io_data = data; |
| listener->io_notify = notify; |
| |
| for (i = 0; i < listener->nsioc; i++) { |
| if (listener->io_tag[i]) { |
| g_source_remove(listener->io_tag[i]); |
| listener->io_tag[i] = 0; |
| } |
| } |
| |
| if (listener->io_func != NULL) { |
| for (i = 0; i < listener->nsioc; i++) { |
| object_ref(OBJECT(listener)); |
| listener->io_tag[i] = qio_channel_add_watch( |
| QIO_CHANNEL(listener->sioc[i]), G_IO_IN, |
| qio_net_listener_channel_func, |
| listener, (GDestroyNotify)object_unref); |
| } |
| } |
| } |
| |
| |
| struct QIONetListenerClientWaitData { |
| QIOChannelSocket *sioc; |
| GMainLoop *loop; |
| }; |
| |
| |
| static gboolean qio_net_listener_wait_client_func(QIOChannel *ioc, |
| GIOCondition condition, |
| gpointer opaque) |
| { |
| struct QIONetListenerClientWaitData *data = opaque; |
| QIOChannelSocket *sioc; |
| |
| sioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc), |
| NULL); |
| if (!sioc) { |
| return TRUE; |
| } |
| |
| if (data->sioc) { |
| object_unref(OBJECT(sioc)); |
| } else { |
| data->sioc = sioc; |
| g_main_loop_quit(data->loop); |
| } |
| |
| return TRUE; |
| } |
| |
| QIOChannelSocket *qio_net_listener_wait_client(QIONetListener *listener) |
| { |
| GMainContext *ctxt = g_main_context_new(); |
| GMainLoop *loop = g_main_loop_new(ctxt, TRUE); |
| GSource **sources; |
| struct QIONetListenerClientWaitData data = { |
| .sioc = NULL, |
| .loop = loop |
| }; |
| size_t i; |
| |
| for (i = 0; i < listener->nsioc; i++) { |
| if (listener->io_tag[i]) { |
| g_source_remove(listener->io_tag[i]); |
| listener->io_tag[i] = 0; |
| } |
| } |
| |
| sources = g_new0(GSource *, listener->nsioc); |
| for (i = 0; i < listener->nsioc; i++) { |
| sources[i] = qio_channel_create_watch(QIO_CHANNEL(listener->sioc[i]), |
| G_IO_IN); |
| |
| g_source_set_callback(sources[i], |
| (GSourceFunc)qio_net_listener_wait_client_func, |
| &data, |
| NULL); |
| g_source_attach(sources[i], ctxt); |
| } |
| |
| g_main_loop_run(loop); |
| |
| for (i = 0; i < listener->nsioc; i++) { |
| g_source_unref(sources[i]); |
| } |
| g_main_loop_unref(loop); |
| g_main_context_unref(ctxt); |
| |
| if (listener->io_func != NULL) { |
| for (i = 0; i < listener->nsioc; i++) { |
| object_ref(OBJECT(listener)); |
| listener->io_tag[i] = qio_channel_add_watch( |
| QIO_CHANNEL(listener->sioc[i]), G_IO_IN, |
| qio_net_listener_channel_func, |
| listener, (GDestroyNotify)object_unref); |
| } |
| } |
| |
| return data.sioc; |
| } |
| |
| void qio_net_listener_disconnect(QIONetListener *listener) |
| { |
| size_t i; |
| |
| if (!listener->connected) { |
| return; |
| } |
| |
| for (i = 0; i < listener->nsioc; i++) { |
| if (listener->io_tag[i]) { |
| g_source_remove(listener->io_tag[i]); |
| listener->io_tag[i] = 0; |
| } |
| qio_channel_close(QIO_CHANNEL(listener->sioc[i]), NULL); |
| } |
| listener->connected = false; |
| } |
| |
| |
| bool qio_net_listener_is_connected(QIONetListener *listener) |
| { |
| return listener->connected; |
| } |
| |
| static void qio_net_listener_finalize(Object *obj) |
| { |
| QIONetListener *listener = QIO_NET_LISTENER(obj); |
| size_t i; |
| |
| qio_net_listener_disconnect(listener); |
| |
| for (i = 0; i < listener->nsioc; i++) { |
| object_unref(OBJECT(listener->sioc[i])); |
| } |
| g_free(listener->io_tag); |
| g_free(listener->sioc); |
| g_free(listener->name); |
| } |
| |
| static const TypeInfo qio_net_listener_info = { |
| .parent = TYPE_OBJECT, |
| .name = TYPE_QIO_NET_LISTENER, |
| .instance_size = sizeof(QIONetListener), |
| .instance_finalize = qio_net_listener_finalize, |
| .class_size = sizeof(QIONetListenerClass), |
| }; |
| |
| |
| static void qio_net_listener_register_types(void) |
| { |
| type_register_static(&qio_net_listener_info); |
| } |
| |
| |
| type_init(qio_net_listener_register_types); |