sgsn: Integrate c-ares with the osmocom event loop

c-ares is an asynchronous DNS resolver and we need it to
resolve the GGSN address. This is integrating the library
into our infrastructure. We will create and maintain a list
of registered FDs (c-ares is currently only using one of
them) and (re-)schedule the timer after events occurred.
diff --git a/openbsc/configure.ac b/openbsc/configure.ac
index 394972f..fb6feb9 100644
--- a/openbsc/configure.ac
+++ b/openbsc/configure.ac
@@ -79,6 +79,11 @@
 AM_CONDITIONAL(HAVE_LIBGTP, test "$found_libgtp" = yes)
 AC_SUBST(found_libgtp)
 
+found_libcares=yes
+PKG_CHECK_MODULES([LIBCARES], [libcares], [], [found_libcares=no])
+AM_CONDITIONAL(HAVE_LIBCARES, test "$found_libcares" = yes)
+AC_SUBST(found_libcares)
+
 dnl checks for header files
 AC_HEADER_STDC
 AC_CHECK_HEADERS(dahdi/user.h,,AC_MSG_WARN(DAHDI input driver will not be built))
diff --git a/openbsc/include/openbsc/sgsn.h b/openbsc/include/openbsc/sgsn.h
index 0f9a59f..8e4d532 100644
--- a/openbsc/include/openbsc/sgsn.h
+++ b/openbsc/include/openbsc/sgsn.h
@@ -7,6 +7,8 @@
 #include <osmocom/gprs/gprs_ns.h>
 #include <openbsc/gprs_sgsn.h>
 
+#include <ares.h>
+
 struct gprs_gsup_client;
 
 enum sgsn_auth_policy {
@@ -58,6 +60,11 @@
 	struct gprs_gsup_client *gsup_client;
 	/* LLME inactivity timer */
 	struct osmo_timer_list llme_timer;
+
+	/* c-ares event loop integration */
+	struct osmo_timer_list ares_timer;
+	struct llist_head ares_fds;
+	ares_channel ares_channel;
 };
 
 extern struct sgsn_instance *sgsn;
@@ -99,4 +106,11 @@
  */
 int sgsn_cdr_init(struct sgsn_instance *sgsn);
 
+
+/*
+ * C-ARES related functionality
+ */
+int sgsn_ares_init(struct sgsn_instance *sgsn);
+int sgsn_ares_query(struct sgsn_instance *sgsm, const char *name, ares_host_callback cb, void *data);
+
 #endif
diff --git a/openbsc/src/gprs/Makefile.am b/openbsc/src/gprs/Makefile.am
index 3d02822..f46a402 100644
--- a/openbsc/src/gprs/Makefile.am
+++ b/openbsc/src/gprs/Makefile.am
@@ -1,16 +1,19 @@
 AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
 AM_CFLAGS=-Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) \
 	$(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOCTRL_CFLAGS) \
-	$(LIBOSMOABIS_CFLAGS) $(LIBOSMOGB_CFLAGS) $(COVERAGE_CFLAGS)
+	$(LIBOSMOABIS_CFLAGS) $(LIBOSMOGB_CFLAGS) $(COVERAGE_CFLAGS) \
+	$(LIBCARES_CFLAGS)
 OSMO_LIBS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) \
 	    $(LIBOSMOCTRL_LIBS) $(LIBOSMOGB_LIBS)
 
 noinst_HEADERS = gprs_sndcp.h
 
-if HAVE_LIBGTP
-bin_PROGRAMS = osmo-gbproxy osmo-sgsn
-else
 bin_PROGRAMS = osmo-gbproxy
+
+if HAVE_LIBGTP
+if HAVE_LIBCARES
+bin_PROGRAMS += osmo-sgsn
+endif
 endif
 
 osmo_gbproxy_SOURCES =  gb_proxy.c gb_proxy_main.c gb_proxy_vty.c \
@@ -24,7 +27,7 @@
 			gprs_llc.c gprs_llc_parse.c gprs_llc_vty.c crc24.c \
 			sgsn_ctrl.c sgsn_auth.c gprs_subscriber.c \
 			gprs_gsup_messages.c gprs_utils.c gprs_gsup_client.c \
-			gsm_04_08_gprs.c sgsn_cdr.c
+			gsm_04_08_gprs.c sgsn_cdr.c sgsn_ares.c
 osmo_sgsn_LDADD = 	\
 			$(top_builddir)/src/libcommon/libcommon.a \
-			-lgtp $(OSMO_LIBS) $(LIBOSMOABIS_LIBS) -lrt
+			-lgtp $(OSMO_LIBS) $(LIBOSMOABIS_LIBS) $(LIBCARES_LIBS) -lrt
diff --git a/openbsc/src/gprs/sgsn_ares.c b/openbsc/src/gprs/sgsn_ares.c
new file mode 100644
index 0000000..825b01c
--- /dev/null
+++ b/openbsc/src/gprs/sgsn_ares.c
@@ -0,0 +1,168 @@
+/* C-ARES DNS resolver integration */
+
+/*
+ * (C) 2015 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/sgsn.h>
+#include <openbsc/debug.h>
+
+#include <netdb.h>
+
+struct cares_event_fd {
+	struct llist_head head;
+	struct osmo_fd fd;
+};
+
+struct cares_cb_data {
+	ares_host_callback cb;
+	void *data;
+};
+
+static void osmo_ares_reschedule(struct sgsn_instance *sgsn);
+static void ares_cb(void *_arg, int status, int timeouts, struct hostent *hostent)
+{
+	struct cares_cb_data *arg = _arg;
+
+	arg->cb(arg->data, status, timeouts, hostent);
+	osmo_ares_reschedule(sgsn);
+	talloc_free(arg);
+}
+
+static int ares_osmo_fd_cb(struct osmo_fd *fd, unsigned int what)
+{
+	LOGP(DGPRS, LOGL_DEBUG, "C-ares fd(%d) ready(%d)\n", fd->fd, what);
+
+	ares_process_fd(sgsn->ares_channel,
+			(what & BSC_FD_READ) ? fd->fd : ARES_SOCKET_BAD,
+			(what & BSC_FD_WRITE) ? fd->fd : ARES_SOCKET_BAD);
+	osmo_ares_reschedule(sgsn);
+	return 0;
+}
+
+static void ares_timeout_cb(void *data)
+{
+	struct sgsn_instance *sgsn = data;
+
+	LOGP(DGPRS, LOGL_DEBUG, "C-ares triggering timeout\n");
+	ares_process_fd(sgsn->ares_channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+	osmo_ares_reschedule(sgsn);
+}
+
+static void osmo_ares_reschedule(struct sgsn_instance *sgsn)
+{
+	struct timeval *timeout, tv;
+
+	osmo_timer_del(&sgsn->ares_timer);
+	timeout = ares_timeout(sgsn->ares_channel, NULL, &tv);
+	if (timeout) {
+		sgsn->ares_timer.cb = ares_timeout_cb;
+		sgsn->ares_timer.data = sgsn;
+
+		LOGP(DGPRS, LOGL_DEBUG, "C-ares scheduling timeout %llu.%llu\n",
+			(unsigned long long) tv.tv_sec,
+			(unsigned long long) tv.tv_usec);
+		osmo_timer_schedule(&sgsn->ares_timer, tv.tv_sec, tv.tv_usec);
+	}
+}
+
+static void setup_ares_osmo_fd(void *data, int fd, int read, int write)
+{
+	struct cares_event_fd *ufd, *tmp;
+
+	/* delete the entry */
+	if (read == 0 && write == 0) {
+		llist_for_each_entry_safe(ufd, tmp, &sgsn->ares_fds, head) {
+			if (ufd->fd.fd != fd)
+				continue;
+
+			LOGP(DGPRS, LOGL_DEBUG,
+				"Removing C-ares watched fd (%d)\n", fd);
+			osmo_fd_unregister(&ufd->fd);
+			llist_del(&ufd->head);
+			talloc_free(ufd);
+			return;
+		}
+	}
+
+	/* Search for the fd or create a new one */
+	llist_for_each_entry(ufd, &sgsn->ares_fds, head) {
+		if (ufd->fd.fd != fd)
+			continue;
+
+		LOGP(DGPRS, LOGL_DEBUG, "Updating C-ares fd (%d)\n", fd);
+		goto update_fd;
+	}
+
+	LOGP(DGPRS, LOGL_DEBUG, "Registering C-ares fd (%d)\n", fd);
+	ufd = talloc_zero(tall_bsc_ctx, struct cares_event_fd);
+	ufd->fd.fd = fd;
+	ufd->fd.cb = ares_osmo_fd_cb;
+	ufd->fd.data = data;
+	osmo_fd_register(&ufd->fd);
+	llist_add(&ufd->head, &sgsn->ares_fds);
+
+update_fd:
+	if (read)
+		ufd->fd.when |= BSC_FD_READ;
+	else
+		ufd->fd.when &= ~BSC_FD_READ;
+
+	if (write)
+		ufd->fd.when |= BSC_FD_WRITE;
+	else
+		ufd->fd.when &= ~BSC_FD_WRITE;
+
+	osmo_ares_reschedule(sgsn);
+}
+
+int sgsn_ares_query(struct sgsn_instance *sgsn, const char *name,
+			ares_host_callback cb, void *data)
+{
+	struct cares_cb_data *cb_data;
+
+	cb_data = talloc_zero(tall_bsc_ctx, struct cares_cb_data);
+	cb_data->cb = cb;
+	cb_data->data = data;
+	ares_gethostbyname(sgsn->ares_channel, name, AF_INET, ares_cb, cb_data);
+	osmo_ares_reschedule(sgsn);
+	return 0;
+}
+
+int sgsn_ares_init(struct sgsn_instance *sgsn)
+{
+	struct ares_options options;
+	int optmask;
+	int rc;
+
+	INIT_LLIST_HEAD(&sgsn->ares_fds);
+	memset(&options, 0, sizeof(options));
+	options.sock_state_cb = setup_ares_osmo_fd;
+	options.sock_state_cb_data = sgsn;
+
+	optmask = ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB;
+
+	/*| ARES_OPT_SERVERS ... TODO..*/
+
+	ares_library_init(ARES_LIB_INIT_ALL);
+	rc = ares_init_options(&sgsn->ares_channel, &options, optmask);
+
+	return rc;
+}
+
+osmo_static_assert(ARES_SUCCESS == 0, ares_success_zero);