aboutsummaryrefslogtreecommitdiffstats
path: root/src/udp_server.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/udp_server.c')
-rw-r--r--src/udp_server.c140
1 files changed, 140 insertions, 0 deletions
diff --git a/src/udp_server.c b/src/udp_server.c
new file mode 100644
index 000000000..f96b40b6b
--- /dev/null
+++ b/src/udp_server.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "udp_server.h"
+#include "io_thread.h"
+#include "glib_socket.h"
+#include "gcc.h"
+
+#include <glib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+#if GCC_CHECK_VERSION(4, 2)
+/* allow C99 initialisers on struct sockaddr_in, even if the
+ (non-portable) attribute "sin_zero" is missing */
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
+struct udp_server {
+ const struct udp_server_handler *handler;
+ void *handler_ctx;
+
+ int fd;
+ GIOChannel *channel;
+ GSource *source;
+
+ char buffer[8192];
+};
+
+static gboolean
+udp_in_event(G_GNUC_UNUSED GIOChannel *source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data)
+{
+ struct udp_server *udp = data;
+
+ struct sockaddr_storage address_storage;
+ struct sockaddr *address = (struct sockaddr *)&address_storage;
+ socklen_t address_length = sizeof(address_storage);
+
+ ssize_t nbytes = recvfrom(udp->fd, udp->buffer, sizeof(udp->buffer),
+#ifdef WIN32
+ 0,
+#else
+ MSG_DONTWAIT,
+#endif
+ address, &address_length);
+ if (nbytes <= 0)
+ return true;
+
+ udp->handler->datagram(udp->fd, udp->buffer, nbytes,
+ address, address_length, udp->handler_ctx);
+ return true;
+}
+
+struct udp_server *
+udp_server_new(unsigned port,
+ const struct udp_server_handler *handler, void *ctx,
+ GError **error_r)
+{
+ int fd = socket(PF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ g_set_error(error_r, udp_server_quark(), errno,
+ "failed to create UDP socket: %s",
+ g_strerror(errno));
+ return NULL;
+ }
+
+ const struct sockaddr_in address = {
+ .sin_family = AF_INET,
+ .sin_addr = {
+ .s_addr = htonl(INADDR_ANY),
+ },
+ .sin_port = htons(port),
+#if defined(__linux__) && !GCC_CHECK_VERSION(4, 2)
+ .sin_zero = { 0 },
+#endif
+ };
+
+ if (bind(fd, (const struct sockaddr *)&address, sizeof(address)) < 0) {
+ g_set_error(error_r, udp_server_quark(), errno,
+ "failed to bind UDP port %u: %s",
+ port, g_strerror(errno));
+ close(fd);
+ return NULL;
+ }
+
+ struct udp_server *udp = g_new(struct udp_server, 1);
+ udp->handler = handler;
+ udp->handler_ctx = ctx;
+
+ udp->fd = fd;
+ udp->channel = g_io_channel_new_socket(fd);
+ /* NULL encoding means the stream is binary safe */
+ g_io_channel_set_encoding(udp->channel, NULL, NULL);
+ /* no buffering */
+ g_io_channel_set_buffered(udp->channel, false);
+
+ udp->source = g_io_create_watch(udp->channel, G_IO_IN);
+ g_source_set_callback(udp->source, (GSourceFunc)udp_in_event, udp,
+ NULL);
+ g_source_attach(udp->source, io_thread_context());
+
+ return udp;
+}
+
+void
+udp_server_free(struct udp_server *udp)
+{
+ g_source_destroy(udp->source);
+ g_source_unref(udp->source);
+ g_io_channel_unref(udp->channel);
+ close(udp->fd);
+ g_free(udp);
+}