Move some setup functions from os_routing to os_net
[oonf.git] / src-api / core / os_linux / os_net_linux.c
index 4b192f2..c17169e 100644 (file)
 #include <net/if.h>
 #include <netinet/in.h>
 #include <sys/ioctl.h>
+#include <sys/utsname.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
 
 #include "common/common_types.h"
 
-#include "core/olsr_interface.h"
 #include "core/olsr_logging.h"
 #include "core/olsr_subsystem.h"
 #include "core/os_net.h"
 
+/* ip forwarding */
+#define PROC_IPFORWARD_V4 "/proc/sys/net/ipv4/ip_forward"
+#define PROC_IPFORWARD_V6 "/proc/sys/net/ipv6/conf/all/forwarding"
+
+/* Redirect proc entry */
+#define PROC_IF_REDIRECT "/proc/sys/net/ipv4/conf/%s/send_redirects"
+#define PROC_ALL_REDIRECT "/proc/sys/net/ipv4/conf/all/send_redirects"
+
+/* IP spoof proc entry */
+#define PROC_IF_SPOOF "/proc/sys/net/ipv4/conf/%s/rp_filter"
+#define PROC_ALL_SPOOF "/proc/sys/net/ipv4/conf/all/rp_filter"
+
 /* prototypes */
 static int _init(void);
 static void _cleanup(void);
+static void _activate_if_routing(void);
+static void _deactivate_if_routing(void);
+static bool _is_at_least_linuxkernel_2_6_31(void);
+static int _os_linux_writeToProc(const char *file, char *old, char value);
 
 /* global ioctl sockets for ipv4 and ipv6 */
 static int _ioctl_v4, _ioctl_v6;
@@ -64,6 +82,15 @@ struct oonf_subsystem oonf_os_net_subsystem = {
   .cleanup = _cleanup,
 };
 
+/* global procfile state before initialization */
+static char _original_rp_filter;
+static char _original_icmp_redirect;
+static char _original_ipv4_forward;
+static char _original_ipv6_forward;
+
+/* counter of mesh interfaces for ip_forward configuration */
+static int _mesh_count = 0;
+
 /**
  * Initialize os_net subsystem
  * @return -1 if an error happened, 0 otherwise
@@ -233,3 +260,225 @@ os_net_update_interface(struct olsr_interface_data *ifdata,
   freeifaddrs(ifaddrs);
   return 0;
 }
+
+/**
+ * Initialize interface for mesh usage
+ * @param interf pointer to interface object
+ * @return -1 if an error happened, 0 otherwise
+ */
+int
+os_net_init_mesh_if(struct olsr_interface *interf) {
+  char procfile[FILENAME_MAX];
+  char old_redirect = 0, old_spoof = 0;
+
+  /* handle global ip_forward setting */
+  _mesh_count++;
+  if (_mesh_count == 1) {
+    _activate_if_routing();
+  }
+
+  /* Generate the procfile name */
+  snprintf(procfile, sizeof(procfile), PROC_IF_REDIRECT, interf->data.name);
+
+  if (_os_linux_writeToProc(procfile, &old_redirect, '0')) {
+    OLSR_WARN(LOG_OS_SYSTEM, "WARNING! Could not disable ICMP redirects! "
+        "You should manually ensure that ICMP redirects are disabled!");
+  }
+
+  /* Generate the procfile name */
+  snprintf(procfile, sizeof(procfile), PROC_IF_SPOOF, interf->data.name);
+
+  if (_os_linux_writeToProc(procfile, &old_spoof, '0')) {
+    OLSR_WARN(LOG_OS_SYSTEM, "WARNING! Could not disable the IP spoof filter! "
+        "You should mannually ensure that IP spoof filtering is disabled!");
+  }
+
+  interf->_original_state = (old_redirect << 8) | (old_spoof);
+  return 0;
+}
+
+/**
+ * Cleanup interface after mesh usage
+ * @param interf pointer to interface object
+ */
+void
+os_net_cleanup_mesh_if(struct olsr_interface *interf) {
+  char restore_redirect, restore_spoof;
+  char procfile[FILENAME_MAX];
+
+  restore_redirect = (interf->_original_state >> 8) & 255;
+  restore_spoof = (interf->_original_state & 255);
+
+  /* Generate the procfile name */
+  snprintf(procfile, sizeof(procfile), PROC_IF_REDIRECT, interf->data.name);
+
+  if (restore_redirect != 0
+      && _os_linux_writeToProc(procfile, NULL, restore_redirect) != 0) {
+    OLSR_WARN(LOG_OS_SYSTEM, "Could not restore ICMP redirect flag %s to %c",
+        procfile, restore_redirect);
+  }
+
+  /* Generate the procfile name */
+  snprintf(procfile, sizeof(procfile), PROC_IF_SPOOF, interf->data.name);
+
+  if (restore_spoof != 0
+      && _os_linux_writeToProc(procfile, NULL, restore_spoof) != 0) {
+    OLSR_WARN(LOG_OS_SYSTEM, "Could not restore IP spoof flag %s to %c",
+        procfile, restore_spoof);
+  }
+
+  /* handle global ip_forward setting */
+  _mesh_count--;
+  if (_mesh_count == 0) {
+    _deactivate_if_routing();
+  }
+
+  interf->_original_state = 0;
+  return;
+}
+
+static void
+_activate_if_routing(void) {
+  if (_os_linux_writeToProc(PROC_IPFORWARD_V4, &_original_ipv4_forward, '1')) {
+    OLSR_WARN(LOG_OS_SYSTEM, "WARNING! Could not activate ip_forward for ipv4! "
+        "You should manually ensure that ip_forward for ipv4 is activated!");
+  }
+  if (_os_linux_writeToProc(PROC_IPFORWARD_V6, &_original_ipv6_forward, '1')) {
+    OLSR_WARN(LOG_OS_SYSTEM, "WARNING! Could not activate ip_forward for ipv6! "
+        "You should manually ensure that ip_forward for ipv6 is activated!");
+  }
+
+  if (_os_linux_writeToProc(PROC_ALL_REDIRECT, &_original_icmp_redirect, '0')) {
+    OLSR_WARN(LOG_OS_SYSTEM, "WARNING! Could not disable ICMP redirects! "
+        "You should manually ensure that ICMP redirects are disabled!");
+  }
+
+  /* check kernel version and disable global rp_filter */
+  if (_is_at_least_linuxkernel_2_6_31()) {
+    if (_os_linux_writeToProc(PROC_ALL_SPOOF, &_original_rp_filter, '0')) {
+      OLSR_WARN(LOG_OS_SYSTEM, "WARNING! Could not disable global rp_filter "
+          "(necessary for kernel 2.6.31 and newer)! You should manually "
+          "ensure that rp_filter is disabled!");
+    }
+  }
+}
+
+static void
+_deactivate_if_routing(void) {
+  if (_original_icmp_redirect != 0
+      && _os_linux_writeToProc(PROC_ALL_REDIRECT, &_original_icmp_redirect, '0') != 0) {
+    OLSR_WARN(LOG_OS_SYSTEM,
+        "WARNING! Could not restore ICMP redirect flag %s to %c!",
+        PROC_ALL_REDIRECT, _original_icmp_redirect);
+  }
+
+  /* check kernel version and disable global rp_filter */
+  if (_os_linux_writeToProc(PROC_ALL_SPOOF, &_original_rp_filter, '0')) {
+    OLSR_WARN(LOG_OS_SYSTEM,
+        "WARNING! Could not restore global rp_filter flag %s to %c!",
+        PROC_ALL_SPOOF, _original_rp_filter);
+  }
+
+  if (_os_linux_writeToProc(PROC_IPFORWARD_V4, NULL, _original_ipv4_forward)) {
+    OLSR_WARN(LOG_OS_SYSTEM, "WARNING! Could not restore %s to %c!",
+        PROC_IPFORWARD_V4, _original_ipv4_forward);
+  }
+  if (_os_linux_writeToProc(PROC_IPFORWARD_V6, NULL, _original_ipv6_forward)) {
+    OLSR_WARN(LOG_OS_SYSTEM, "WARNING! Could not restore flag %s to %c",
+        PROC_IPFORWARD_V6, _original_ipv6_forward);
+  }
+}
+
+
+/**
+ * Overwrite a numeric entry in the procfile system and keep the old
+ * value.
+ * @param file pointer to filename (including full path)
+ * @param old pointer to memory to store old value
+ * @param value new value
+ * @return -1 if an error happened, 0 otherwise
+ */
+static int
+_os_linux_writeToProc(const char *file, char *old, char value) {
+  int fd;
+  char rv;
+
+  if ((fd = open(file, O_RDWR)) < 0) {
+    goto writetoproc_error;
+  }
+
+  if (read(fd, &rv, 1) != 1) {
+    goto writetoproc_error;
+  }
+
+  if (rv != value) {
+    if (lseek(fd, SEEK_SET, 0) == -1) {
+      goto writetoproc_error;
+    }
+
+    if (write(fd, &value, 1) != 1) {
+      goto writetoproc_error;
+    }
+
+    OLSR_DEBUG(LOG_OS_SYSTEM, "Writing '%c' (was %c) to %s", value, rv, file);
+  }
+
+  if (close(fd) != 0) {
+    goto writetoproc_error;
+  }
+
+  if (old && rv != value) {
+    *old = rv;
+  }
+
+  return 0;
+
+writetoproc_error:
+  OLSR_WARN(LOG_OS_SYSTEM,
+    "Error, cannot read proc entry %s: %s (%d)\n",
+    file, strerror(errno), errno);
+  return -1;
+}
+
+/**
+ * @return true if linux kernel is at least 2.6.31
+ */
+static bool
+_is_at_least_linuxkernel_2_6_31(void) {
+  struct utsname uts;
+  char *next;
+  int first = 0, second = 0, third = 0;
+
+  memset(&uts, 0, sizeof(uts));
+  if (uname(&uts)) {
+    OLSR_WARN(LOG_OS_SYSTEM,
+        "Error, could not read kernel version: %s (%d)\n",
+        strerror(errno), errno);
+    return false;
+  }
+
+  first = strtol(uts.release, &next, 10);
+  /* check for linux 3.x */
+  if (first >= 3) {
+    return true;
+  }
+
+  if (*next != '.') {
+    goto kernel_parse_error;
+  }
+
+  second = strtol(next+1, &next, 10);
+  if (*next != '.') {
+    goto kernel_parse_error;
+  }
+
+  third = strtol(next+1, NULL, 10);
+
+  /* better or equal than linux 2.6.31 ? */
+  return first == 2 && second == 6 && third >= 31;
+
+kernel_parse_error:
+  OLSR_WARN(LOG_OS_SYSTEM,
+      "Error, cannot parse kernel version: %s\n", uts.release);
+  return false;
+}