# HG changeset patch
# Node ID 4d54fd2002e8
# Parent  03729a540aac
diff --git a/meson.build b/meson.build
--- a/meson.build
+++ b/meson.build
@@ -1,8 +1,8 @@
 project('libtraversity', 'C',
-    license : 'LGPL-2.0-or-later',
-    version : '0.0.1',
-    meson_version : '>=0.63.0',
-    default_options : ['c_std=c99', 'warning_level=2'])
+	license : 'LGPL-2.0-or-later',
+	version : '0.0.1',
+	meson_version : '>=0.63.0',
+	default_options : ['c_std=c99', 'warning_level=2'])
 
 ###############################################################################
 # Version Stuff
@@ -11,9 +11,9 @@
 
 parts = meson.project_version().split('-')
 if parts.length() > 1
-    traversity_extra_version = parts[1]
+	traversity_extra_version = parts[1]
 else
-    traversity_extra_version = ''
+	traversity_extra_version = ''
 endif
 
 parts = parts[0].split('.')
@@ -25,6 +25,12 @@
                                              traversity_minor_version,
                                              traversity_micro_version)
 
+conf = configuration_data()
+conf.set_quoted('PACKAGE', meson.project_name())
+conf.set_quoted('PACKAGE_NAME', meson.project_name())
+conf.set_quoted('VERSION', meson.project_version())
+conf.set_quoted('DISPLAY_VERSION', meson.project_version())
+
 version_conf = configuration_data()
 version_conf.set('TRAVERSITY_MAJOR_VERSION', traversity_major_version)
 version_conf.set('TRAVERSITY_MINOR_VERSION', traversity_minor_version)
@@ -49,9 +55,11 @@
 ###############################################################################
 GLIB = dependency('glib-2.0', version : '>=2.70.0')
 add_project_arguments(
-    '-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_70',
-    '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_70',
-    language : 'c',
+	'-D_GNU_SOURCE',
+	'-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_70',
+	'-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_70',
+	'-DHAVE_CONFIG_H=1',
+	language : 'c',
 )
 
 GOBJECT = dependency('gobject-2.0', version : '>=2.70.0')
@@ -59,16 +67,24 @@
 TEMPLATE_GLIB = dependency('template-glib-1.0', version : '>=3.35.0')
 
 ###############################################################################
+# compile features
+###############################################################################
+cc = meson.get_compiler('c')
+
+conf.set('HAVE_GETIFADDRS', cc.has_function('getifaddrs'))
+conf.set('HAVE_INET_NTOP', cc.has_function('inet_ntop'))
+
+###############################################################################
 # Documentation
 ###############################################################################
 if get_option('doc') and not get_option('introspection')
-    error('Documentation requires GObject Introspection.')
+	error('Documentation requires GObject Introspection.')
 endif
 
 gidocgen_dep = dependency(
-    'gi-docgen', version: '>= 2021.1',
-    fallback: ['gi-docgen', 'dummy_dep'],
-    required: get_option('doc')
+	'gi-docgen', version: '>= 2021.1',
+	fallback: ['gi-docgen', 'dummy_dep'],
+	required: get_option('doc')
 )
 
 gidocgen = find_program('gi-docgen', required : get_option('doc'))
@@ -79,3 +95,6 @@
 ###############################################################################
 subdir('traversity')
 subdir('traversity-test')
+
+# Output our configuration file.
+configure_file(output : 'config.h', configuration : conf)
diff --git a/traversity-test/traversity-test.c b/traversity-test/traversity-test.c
--- a/traversity-test/traversity-test.c
+++ b/traversity-test/traversity-test.c
@@ -66,10 +66,29 @@
 gint
 main(G_GNUC_UNUSED gint argc, G_GNUC_UNUSED gchar *argv[]) {
 	TraversityDiscoverer *discoverer = NULL;
+	GError *error = NULL;
+	GList *ips = NULL;
 	GMainLoop *loop = NULL;
 
 	loop = g_main_loop_new(NULL, FALSE);
 
+	g_message("local ip addresses:");
+	ips = traversity_core_get_all_local_ips(&error);
+	if(error != NULL) {
+		g_message(" - error: %s", error->message);
+		g_clear_error(&error);
+	}
+	if(ips == NULL) {
+		g_message(" - none");
+	}
+	while(ips != NULL) {
+		char *ip = (char *)ips->data;
+		g_message(" - %s", ip);
+
+		ips = g_list_delete_link(ips, ips);
+		g_free(ip);
+	}
+
 	discoverer = g_object_new(TRAVERSITY_TYPE_UPNP_DISCOVERER, NULL);
 	g_signal_connect(discoverer, "notify::status",
 	                 G_CALLBACK(traversity_test_notify_status_cb), loop);
diff --git a/traversity/meson.build b/traversity/meson.build
--- a/traversity/meson.build
+++ b/traversity/meson.build
@@ -17,6 +17,17 @@
 TRAVERSITY_BUILT_SOURCES = []
 TRAVERSITY_BUILT_HEADERS = []
 
+TRAVERSITY_DEPENDENCIES = [GLIB, GOBJECT, TEMPLATE_GLIB]
+
+if host_machine.system() == 'windows'
+else
+	TRAVERSITY_SOURCES += 'traversitycoreunix.c'
+endif
+
+if host_machine.system() == 'haiku'
+	TRAVERSITY_DEPENDENCIES += cc.find_library('network')
+endif
+
 traversity_filebase = 'traversity-1'
 traversity_include_base = traversity_filebase / 'traversity'
 
@@ -61,7 +72,7 @@
 
 libtraversity = library('traversity',
 	TRAVERSITY_ALL_SOURCES,
-	dependencies : [GLIB, GOBJECT, TEMPLATE_GLIB],
+	dependencies : TRAVERSITY_DEPENDENCIES,
 	c_args : TRAVERSITY_CFLAGS,
 	version : LIBTRAVERSITY_VERSION,
 	include_directories : [toplevel_inc, traversity_inc],
@@ -72,7 +83,7 @@
     sources : TRAVERSITY_BUILT_HEADERS,
     include_directories : [toplevel_inc, traversity_inc],
     link_with : libtraversity,
-    dependencies : [GLIB, GOBJECT, TEMPLATE_GLIB])
+    dependencies : TRAVERSITY_DEPENDENCIES)
 
 meson.override_dependency(traversity_filebase, traversity_dep)
 
diff --git a/traversity/traversitycore.h b/traversity/traversitycore.h
--- a/traversity/traversitycore.h
+++ b/traversity/traversitycore.h
@@ -49,6 +49,19 @@
  */
 void traversity_core_uninit(void);
 
+/**
+ * traversity_core_get_all_local_ips:
+ * @error: (nullable) (optional): A return address for a [type@GLib.GError].
+ *
+ * Gets a list of all of the local ip addresses.
+ *
+ * Returns: (transfer full) (element-type utf8): The list of all local IP
+ *          addresses.
+ *
+ * Since: 1.0.0
+ */
+GList *traversity_core_get_all_local_ips(GError **error);
+
 G_END_DECLS
 
 #endif /* TRAVERSITY_CORE_H */
diff --git a/traversity/traversitycoreunix.c b/traversity/traversitycoreunix.c
new file mode 100644
--- /dev/null
+++ b/traversity/traversitycoreunix.c
@@ -0,0 +1,200 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+/* This is used to test the ioctl method and should never be defined in a
+ * commit.
+ */
+/* #define FORCE_IOCTL */
+
+#ifdef FORCE_IOCTL
+# warning Forcing use of SIOCGIFCONF ioctl to list local ip addresses!
+#endif /* FORCE_IOCTL */
+
+# include <arpa/inet.h>
+# include <net/if.h>
+
+#if defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP) && !defined(FORCE_IOCTL)
+# include <errno.h>
+# include <ifaddrs.h>
+# include <netinet/in.h>
+#else /* defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP) && !defined(FORCE_IOCTL) */
+# include <arpa/nameser.h>
+# include <netinet/in.h>
+# include <sys/ioctl.h>
+# include <sys/socket.h>
+# include <unistd.h>
+#endif /* defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP) && !defined(FORCE_IOCTL) */
+
+#ifdef __HAIKU__
+# ifndef SIOCGIFCONF
+#  include <sys/sockio.h>
+# endif
+#endif
+
+#include "traversitycore.h"
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+#if defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP) && !defined(FORCE_IOCTL)
+GList *
+traversity_core_get_all_local_ips(GError **error) {
+	GList *ips = NULL;
+	struct ifaddrs *first = NULL;
+	struct ifaddrs *ifa = NULL;
+	int ret;
+
+	ret = getifaddrs(&first);
+	if(ret < 0) {
+		g_set_error(error, TRAVERSITY_DOMAIN, 0,
+		            "failed to get network interfaces: %s", g_strerror(errno));
+
+		return NULL;
+	}
+
+	for(ifa = first; ifa != NULL; ifa = ifa->ifa_next) {
+		char host[46];
+		const char *tmp = NULL;
+		int family = AF_UNSPEC;
+
+		if(ifa->ifa_addr == NULL) {
+			continue;
+		}
+
+		family = ifa->ifa_addr->sa_family;
+
+		/* If the interface doesn't support IPv4 or IPv6 skip it. */
+		if(family != AF_INET && family != AF_INET6) {
+			continue;
+		}
+
+		/* If this is a loopback device skip it. */
+		if(ifa->ifa_flags & IFF_LOOPBACK) {
+			continue;
+		}
+
+		if(family == AF_INET) {
+			/* If this is a non-null IPv4 address add it to our list. */
+			struct sockaddr_in *in = (struct sockaddr_in *)ifa->ifa_addr;
+
+			tmp = inet_ntop(family, &in->sin_addr, host, sizeof(host));
+		} else {
+			struct sockaddr_in6 *in = (struct sockaddr_in6 *)ifa->ifa_addr;
+
+			/* Peer-peer link-local communication is a big TODO.  I am not sure
+			 * how communicating link-local addresses is supposed to work, and
+			 * it seems like it would require attempting the cartesian product
+			 * of the local and remote interfaces to see if any match (eww).
+			 */
+			if(!IN6_IS_ADDR_LINKLOCAL(&in->sin6_addr)) {
+				tmp = inet_ntop(family, &in->sin6_addr, host, sizeof(host));
+				if(tmp == NULL) {
+					g_set_error(error, TRAVERSITY_DOMAIN, 0,
+					            "failed to parse IP address: %s",
+					            g_strerror(errno));
+					g_list_free_full(ips, g_free);
+
+					return NULL;
+				}
+			}
+		}
+
+		if(tmp != NULL) {
+			ips = g_list_prepend(ips, g_strdup(tmp));
+		}
+	}
+
+	freeifaddrs(first);
+
+	return g_list_reverse(ips);
+}
+
+#else /* defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP) */
+
+/* Note: This version is used by Haiku and does work on other unixes, but not
+ * OpenBSD.
+ */
+GList *
+traversity_core_get_all_local_ips(GError **error) {
+	GList *ips = NULL;
+	struct ifconf ifc;
+	struct ifreq *ifreq = NULL;
+	int ret = 0;
+	int source = 0;
+
+	/* Create a socket to use in the ioctl call. */
+	if((source = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
+		g_set_error(error, TRAVERSITY_DOMAIN, 0,
+		            "failed to create socket: %s", g_strerror(errno));
+
+		return NULL;
+	}
+
+	/* Call the ioctl to get the size of the buffer for all interfaces.
+	 *
+	 * Note: SIOCGIFCONF only supports IPv4.
+	 */
+	ifc.ifc_len = 0;
+	ifc.ifc_req = NULL;
+	if((ret = ioctl(source, SIOCGIFCONF, &ifc)) < 0) {
+		g_set_error(error, TRAVERSITY_DOMAIN, 0,
+		            "failed to query network interfaces: %s",
+		            g_strerror(ret));
+
+		return NULL;
+	}
+
+	/* ifc_len was set to the total size needed in the above call. */
+	ifc.ifc_req = g_malloc(ifc.ifc_len);
+	if((ret = ioctl(source, SIOCGIFCONF, &ifc)) < 0) {
+		g_set_error(error, TRAVERSITY_DOMAIN, 0,
+		            "failed to query network interfaces: %s",
+		            g_strerror(ret));
+
+		return NULL;
+	}
+
+	/* Close the socket as we no longer need it. */
+	close(source);
+
+	/* Walk through each interface in our list. Item sizes can vary, so we
+	 * add the size as one of the last steps in the body.
+	 */
+	ifreq = ifc.ifc_req;
+	for(int i = 0; i < ifc.ifc_len;) {
+		char dst[46];
+		int len = 0;
+
+#ifndef __linux__
+		len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
+#else
+		len = sizeof(*ifreq);
+#endif
+
+		if(ifreq->ifr_addr.sa_family == AF_INET) {
+			struct sockaddr_in *sinptr = (struct sockaddr_in *)&ifreq->ifr_addr;
+			const char *tmp = NULL;
+
+			tmp = inet_ntop(AF_INET, &sinptr->sin_addr, dst, sizeof(dst));
+			if(tmp == NULL) {
+				g_set_error(error, TRAVERSITY_DOMAIN, 0,
+				            "failed to parse IP address: %s",
+				            g_strerror(errno));
+				g_list_free_full(ips, g_free);
+
+				return NULL;
+			}
+			if(g_strcmp0(dst, "127.0.0.1") != 0) {
+				ips = g_list_append(ips, g_strdup(dst));
+			}
+		}
+
+		ifreq=(struct ifreq *)((char *)ifreq + len);
+		i += len;
+	}
+	g_free(ifc.ifc_req);
+
+	return ips;
+}
+#endif /* defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP) */
