traversity/traversity

099f256aa59a
Parents 775a808aea3b
Children 3bf2cf8b4494
Implement traversity_core_get_local_ips for UNIX systems

This is basically an import from the original code in purple2 with some clean
ups.

Testing Done:
ran traversity-test on Debian Bookworm AMD64, OpenBSD 7.1 64bit, Freebsd 13.1 32bit, macOS Monterey 64bit, and Haiku OS (not sure version/arch).

I also force the ioctl path on all of them, Haiku only uses the ioctl path, but OpenBSD does not support the ioctl path.

Reviewed at https://reviews.imfreedom.org/r/1963/
--- a/meson.build Tue Oct 25 20:35:39 2022 -0500
+++ b/meson.build Mon Nov 07 22:00:16 2022 -0600
@@ -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.30.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)
--- a/traversity-test/traversity-test.c Tue Oct 25 20:35:39 2022 -0500
+++ b/traversity-test/traversity-test.c Mon Nov 07 22:00:16 2022 -0600
@@ -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);
--- a/traversity/meson.build Tue Oct 25 20:35:39 2022 -0500
+++ b/traversity/meson.build Mon Nov 07 22:00:16 2022 -0600
@@ -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)
--- a/traversity/traversitycore.h Tue Oct 25 20:35:39 2022 -0500
+++ b/traversity/traversitycore.h Mon Nov 07 22:00:16 2022 -0600
@@ -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 */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/traversity/traversitycoreunix.c Mon Nov 07 22:00:16 2022 -0600
@@ -0,0 +1,285 @@
+#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
+
+/* Calling sizeof(struct ifreq) isn't always correct on Mac OS X (and maybe
+ * others). This originated from gaim.
+ */
+#ifdef _SIZEOF_ADDR_IFREQ
+# define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
+#else
+# define HX_SIZE_OF_IFREQ(a) sizeof(a)
+#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);
+
+ freeifaddrs(first);
+
+ 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) */
+
+#ifndef __HAIKU__
+/* Note: This version does not work on macOS or Haiku. */
+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(errno));
+
+ 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(errno));
+
+ g_free(ifc.ifc_req);
+
+ 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;
+
+ len = HX_SIZE_OF_IFREQ(*ifreq);
+
+ 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);
+
+ g_free(ifc.ifc_req);
+
+ 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;
+}
+
+#else /* __HAIKU__ */
+
+GList *
+traversity_core_get_all_local_ips(GError **error) {
+ GList *ips = NULL;
+ struct ifconf ifc;
+ struct ifreq *ifr = NULL;
+ char buffer[4096];
+ char *tmp = 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;
+ }
+
+ /* Setup our output buffer. */
+ ifc.ifc_len = sizeof(buffer);
+ ifc.ifc_req = (struct ifreq *)buffer;
+
+ /* Call the ioctl to get all of the interfaces.
+ *
+ * Note: SIOCGIFCONF only supports IPv4.
+ */
+ if((ret = ioctl(source, SIOCGIFCONF, &ifc)) < 0) {
+ g_set_error(error, TRAVERSITY_DOMAIN, 0,
+ "failed to query network interfaces: %s",
+ g_strerror(errno));
+
+ return NULL;
+ }
+
+ /* Close the socket as we no longer need it. */
+ close(source);
+
+ /* Walk through each interface in our list. */
+ tmp = buffer;
+ while(tmp < buffer + ifc.ifc_len) {
+ char dst[46];
+
+ /* Update ifr to point to the index of our buffer. */
+ ifr = (struct ifreq *)tmp;
+
+ /* Move in the index in the buffer to the next record. */
+ tmp += HX_SIZE_OF_IFREQ(*ifr);
+
+ if(ifr->ifr_addr.sa_family == AF_INET) {
+ const char *ret = NULL;
+
+ struct sockaddr_in *sinptr = (struct sockaddr_in *)&ifr->ifr_addr;
+ ret = inet_ntop(AF_INET, &sinptr->sin_addr, dst, sizeof(dst));
+ if(ret == NULL) {
+ g_set_error(error, TRAVERSITY_DOMAIN, 0,
+ "failed to convert 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));
+ }
+ }
+ }
+
+ return ips;
+}
+#endif /* __HAIKU__ */
+
+#endif /* defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP) */