diff --git a/package/gluon-alfred/Makefile b/package/gluon-alfred/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..bb6926a5499546a1e59660f1d5304aa4f5be400e
--- /dev/null
+++ b/package/gluon-alfred/Makefile
@@ -0,0 +1,32 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-alfred
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-alfred
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  DEPENDS:=+gluon-core +gluon-announce +gluon-cron +alfred
+  TITLE:=Configure alfred
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-alfred/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-alfred))
diff --git a/package/gluon-alfred/files/lib/gluon/cron/alfred b/package/gluon-alfred/files/lib/gluon/cron/alfred
new file mode 100644
index 0000000000000000000000000000000000000000..9f79bc640b1d496a62b5a7a9e791e95e496955e8
--- /dev/null
+++ b/package/gluon-alfred/files/lib/gluon/cron/alfred
@@ -0,0 +1 @@
+* * * * * /lib/gluon/announce/collect.lua nodeinfo | gzip | alfred -s 158; /lib/gluon/announce/collect.lua statistics | gzip | alfred -s 159; /lib/gluon/announce/collect.lua neighbours | gzip | alfred -s 160
diff --git a/package/gluon-alfred/files/lib/gluon/upgrade/500-enable-alfred b/package/gluon-alfred/files/lib/gluon/upgrade/500-enable-alfred
new file mode 100755
index 0000000000000000000000000000000000000000..8c52d8a6cb129d67a9176a915472bc2136fb1a23
--- /dev/null
+++ b/package/gluon-alfred/files/lib/gluon/upgrade/500-enable-alfred
@@ -0,0 +1,19 @@
+#!/usr/bin/lua
+
+local uci = require 'luci.model.uci'
+local c = uci.cursor()
+
+
+c:delete('alfred', 'alfred')
+c:section('alfred', 'alfred', 'alfred',
+	  {
+		  interface = 'br-client',
+		  mode = 'slave',
+		  batmanif = 'bat0',
+		  start_vis = '1',
+		  run_facters = '0',
+	  }
+)
+
+c:save('alfred')
+c:commit('alfred')
diff --git a/package/gluon-announce/Makefile b/package/gluon-announce/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3f62c88950672c3dc438efec6ffb392e07b099ed
--- /dev/null
+++ b/package/gluon-announce/Makefile
@@ -0,0 +1,32 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-announce
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-announce
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  DEPENDS:=+gluon-core +luci-lib-json +lua-ethtool-stats
+  TITLE:=Lua scripts announcing various information
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-announce/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-announce))
diff --git a/package/gluon-announce/files/lib/gluon/announce/collect.lua b/package/gluon-announce/files/lib/gluon/announce/collect.lua
new file mode 100755
index 0000000000000000000000000000000000000000..e2974b4988994e7bfb87cb2fc19a426f0dada3ab
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/collect.lua
@@ -0,0 +1,10 @@
+#!/usr/bin/lua
+
+local announce = require 'gluon.announce'
+local json = require 'luci.json'
+local ltn12 = require 'luci.ltn12'
+
+local announce_dir = '/lib/gluon/announce/' .. arg[1] .. '.d'
+
+encoder = json.Encoder(announce.collect_dir(announce_dir))
+ltn12.pump.all(encoder:source(), ltn12.sink.file(io.stdout))
diff --git a/package/gluon-announce/files/lib/gluon/announce/neighbours.d/node_id b/package/gluon-announce/files/lib/gluon/announce/neighbours.d/node_id
new file mode 100644
index 0000000000000000000000000000000000000000..66303f4c779ea43a672d4f20fac307384b15e57f
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/neighbours.d/node_id
@@ -0,0 +1 @@
+return require('gluon.util').node_id()
diff --git a/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/hardware/model b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/hardware/model
new file mode 100644
index 0000000000000000000000000000000000000000..aee3cd81b764271e067b531f4cb5c6d3865bdfff
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/hardware/model
@@ -0,0 +1 @@
+return require('platform_info').get_model()
diff --git a/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/hostname b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/hostname
new file mode 100644
index 0000000000000000000000000000000000000000..7d4f0521861b65384e01a566c38249c1b3b72ac7
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/hostname
@@ -0,0 +1 @@
+return uci:get_first('system', 'system', 'hostname')
diff --git a/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/network/mac b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/network/mac
new file mode 100644
index 0000000000000000000000000000000000000000..049eea58f94a914ce2f14456e92943505e4fdaaf
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/network/mac
@@ -0,0 +1 @@
+return require('gluon.sysconfig').primary_mac
diff --git a/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/node_id b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/node_id
new file mode 100644
index 0000000000000000000000000000000000000000..66303f4c779ea43a672d4f20fac307384b15e57f
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/node_id
@@ -0,0 +1 @@
+return require('gluon.util').node_id()
diff --git a/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/software/firmware b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/software/firmware
new file mode 100644
index 0000000000000000000000000000000000000000..cf50f79fc67b6c1c72a749c07541fc3991d9369e
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/software/firmware
@@ -0,0 +1,4 @@
+return {
+	base = 'gluon-' .. util.trim(fs.readfile('/lib/gluon/gluon-version')),
+	release = util.trim(fs.readfile('/lib/gluon/release')),
+}
diff --git a/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/system/site_code b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/system/site_code
new file mode 100644
index 0000000000000000000000000000000000000000..876fb6d89cba62700ebb1c78a48a908bee3fdc9a
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/system/site_code
@@ -0,0 +1,3 @@
+local site = require 'gluon.site_config'
+
+return site.site_code
diff --git a/package/gluon-announce/files/lib/gluon/announce/statistics.d/idletime b/package/gluon-announce/files/lib/gluon/announce/statistics.d/idletime
new file mode 100644
index 0000000000000000000000000000000000000000..845de268074766e91a7c5d8d52085ae0846f286e
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/statistics.d/idletime
@@ -0,0 +1 @@
+return tonumber(fs.readfile('/proc/uptime'):match('^[^ ]+ ([^ ]+)'))
diff --git a/package/gluon-announce/files/lib/gluon/announce/statistics.d/loadavg b/package/gluon-announce/files/lib/gluon/announce/statistics.d/loadavg
new file mode 100644
index 0000000000000000000000000000000000000000..d79973aa7d405cfea3a920416651f68b84a09010
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/statistics.d/loadavg
@@ -0,0 +1 @@
+return tonumber(fs.readfile('/proc/loadavg'):match('^([^ ]+) '))
diff --git a/package/gluon-announce/files/lib/gluon/announce/statistics.d/memory b/package/gluon-announce/files/lib/gluon/announce/statistics.d/memory
new file mode 100644
index 0000000000000000000000000000000000000000..7b07a1079e0dfe3442c7d939613e521161c824b1
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/statistics.d/memory
@@ -0,0 +1,13 @@
+local data = fs.readfile('/proc/meminfo')
+
+local fields = {}
+for k, v in data:gmatch('([^\n:]+):%s*(%d+) kB') do
+	fields[k] = tonumber(v)
+end
+
+return {
+	total = fields.MemTotal,
+	free = fields.MemFree,
+	buffers = fields.Buffers,
+	cached = fields.Cached,
+}
diff --git a/package/gluon-announce/files/lib/gluon/announce/statistics.d/node_id b/package/gluon-announce/files/lib/gluon/announce/statistics.d/node_id
new file mode 100644
index 0000000000000000000000000000000000000000..66303f4c779ea43a672d4f20fac307384b15e57f
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/statistics.d/node_id
@@ -0,0 +1 @@
+return require('gluon.util').node_id()
diff --git a/package/gluon-announce/files/lib/gluon/announce/statistics.d/processes b/package/gluon-announce/files/lib/gluon/announce/statistics.d/processes
new file mode 100644
index 0000000000000000000000000000000000000000..33ecff668324e9b762a8d8fb1d2eb842415520cd
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/statistics.d/processes
@@ -0,0 +1,3 @@
+local running, total = fs.readfile('/proc/loadavg'):match('^[^ ]+ [^ ]+ [^ ]+ (%d+)/(%d+)')
+
+return { running = tonumber(running), total = tonumber(total) }
diff --git a/package/gluon-announce/files/lib/gluon/announce/statistics.d/rootfs_usage b/package/gluon-announce/files/lib/gluon/announce/statistics.d/rootfs_usage
new file mode 100644
index 0000000000000000000000000000000000000000..8426e9e1c27a663839589d6fb00bd5036a3490ab
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/statistics.d/rootfs_usage
@@ -0,0 +1,4 @@
+local fs = require "nixio.fs"
+
+local st = fs.statvfs("/")
+return 1 - st.bfree / st.blocks
diff --git a/package/gluon-announce/files/lib/gluon/announce/statistics.d/uptime b/package/gluon-announce/files/lib/gluon/announce/statistics.d/uptime
new file mode 100644
index 0000000000000000000000000000000000000000..0bc45beaeae3bdb55973289a1862b1fad65be0a9
--- /dev/null
+++ b/package/gluon-announce/files/lib/gluon/announce/statistics.d/uptime
@@ -0,0 +1 @@
+return tonumber(fs.readfile('/proc/uptime'):match('^([^ ]+) '))
diff --git a/package/gluon-announce/files/usr/lib/lua/gluon/announce.lua b/package/gluon-announce/files/usr/lib/lua/gluon/announce.lua
new file mode 100755
index 0000000000000000000000000000000000000000..7f3f62e285f7e82541d4438acfa62e7f3aba4813
--- /dev/null
+++ b/package/gluon-announce/files/usr/lib/lua/gluon/announce.lua
@@ -0,0 +1,33 @@
+#!/usr/bin/lua
+
+module('gluon.announce', package.seeall)
+
+fs = require 'luci.fs'
+uci = require('luci.model.uci').cursor()
+util = require 'luci.util'
+
+local function collect_entry(entry)
+	if fs.isdirectory(entry) then
+		return collect_dir(entry)
+	else
+		return setfenv(loadfile(entry), _M)()
+	end
+end
+
+function collect_dir(dir)
+	local ret = {}
+
+	for _, entry in ipairs(fs.dir(dir)) do
+		if entry:sub(1, 1) ~= '.' then
+			local ok, val = pcall(collect_entry, dir .. '/' .. entry)
+			if ok then
+				ret[entry] = val
+			else
+				io.stderr:write(val, '\n')
+			end
+		end
+	end
+
+	return ret
+end
+
diff --git a/package/gluon-announced/Makefile b/package/gluon-announced/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..1f802dc2e4432991b5085d53841af9b7b25059b1
--- /dev/null
+++ b/package/gluon-announced/Makefile
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-announced
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-announced
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=announced support
+  DEPENDS:=+gluon-announce
+endef
+
+define Package/gluon-announced/description
+	Gluon community wifi mesh firmware framework: announced support
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+	$(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
+endef
+
+define Package/gluon-announced/install
+	$(CP) ./files/* $(1)/
+	$(INSTALL_DIR) $(1)/usr/bin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-announced $(1)/usr/bin/
+endef
+
+$(eval $(call BuildPackage,gluon-announced))
diff --git a/package/gluon-announced/files/etc/hotplug.d/iface/10-gluon-announced b/package/gluon-announced/files/etc/hotplug.d/iface/10-gluon-announced
new file mode 100644
index 0000000000000000000000000000000000000000..4e8635bd9694d365f38903bfc73dd358d1fe9e8b
--- /dev/null
+++ b/package/gluon-announced/files/etc/hotplug.d/iface/10-gluon-announced
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+. /usr/share/libubox/jshn.sh
+. /lib/functions/service.sh
+
+DEVLIST=/var/run/gluon-announced.devs
+DAEMON=/usr/bin/gluon-announced
+
+ifname_to_dev () {
+	json_load "$(ubus call network.interface.$1 status)"
+	json_get_var dev device
+
+	echo "$dev"
+}
+
+restart_announced () {
+	SERVICE_USE_PID=1
+	SERVICE_WRITE_PID=1
+	SERVICE_DAEMONIZE=1
+
+	DEVS=$(cat $DEVLIST | while read dev iface;do echo -n " -i $dev";done)
+
+	service_stop $DAEMON
+	service_start $DAEMON -g ff02:0:0:0:0:0:2:1001 -p 1001 -s '/lib/gluon/announce/collect.lua nodeinfo' $DEVS
+}
+
+case "$ACTION" in
+	ifdown)
+		sed -i "/$INTERFACE/d" $DEVLIST
+		;;
+	ifup)
+		DEVICE=$(ifname_to_dev $INTERFACE)
+		MESH=$(cat /sys/class/net/$DEVICE/batman_adv/mesh_iface)
+
+		[ $MESH = "bat0" ] || exit 0
+
+		DEVS="$(cat $DEVLIST; echo $DEVICE $INTERFACE)"
+
+		echo "$DEVS" | sort | uniq > $DEVLIST
+
+		restart_announced
+
+		;;
+esac
+
diff --git a/package/gluon-announced/src/Makefile b/package/gluon-announced/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..73e7a9e2172a2d7ea005a359bc129d8edd2ec570
--- /dev/null
+++ b/package/gluon-announced/src/Makefile
@@ -0,0 +1,6 @@
+all: gluon-announced
+
+gluon-announced: gluon-announced.c
+
+clean:
+	rm gluon-announced
diff --git a/package/gluon-announced/src/gluon-announced.c b/package/gluon-announced/src/gluon-announced.c
new file mode 100644
index 0000000000000000000000000000000000000000..27de6ecec8b1040274c09a549f97817f1dfaf46a
--- /dev/null
+++ b/package/gluon-announced/src/gluon-announced.c
@@ -0,0 +1,221 @@
+/*
+   Copyright (c) 2014, Nils Schneider <nils@nilsschneider.net>
+   All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+   */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+void usage() {
+  puts("Usage: gluon-announced [-h] -g <group> -p <port> -i <if0> [-i <if1> ..] -s <script>");
+  puts("  -g <ip6>         multicast group, e.g. ff02:0:0:0:0:0:2:1001");
+  puts("  -p <int>         port number to listen on");
+  puts("  -i <string>      interface on which the group is joined");
+  puts("  -s <string>      script to be executed for each request");
+  puts("  -h               this help\n");
+}
+
+/* The maximum size of output returned is limited to 8192 bytes (including
+ * terminating null byte) for now. If this turns out to be problem, a
+ * dynamic buffer should be implemented instead of increasing the
+ * limit.
+ */
+#define BUFFER 8192
+
+char *run_script(size_t *length, const char *script) {
+  FILE *f;
+  char *buffer;
+
+  buffer = calloc(BUFFER, sizeof(char));
+
+  if (buffer == NULL) {
+    fprintf(stderr, "couldn't allocate buffer\n");
+    return NULL;
+  }
+
+  f = popen(script, "r");
+
+  size_t read_bytes = 0;
+  while (1) {
+    ssize_t ret = fread(buffer+read_bytes, sizeof(char), BUFFER-read_bytes, f);
+
+    if (ret <= 0)
+      break;
+
+    read_bytes += ret;
+  }
+
+  int ret = pclose(f);
+
+  if (ret != 0)
+    fprintf(stderr, "script exited with status %d\n", ret);
+
+  *length = read_bytes;
+
+  return buffer;
+}
+
+void join_mcast(const int sock, const struct in6_addr addr, const char *iface) {
+  struct ipv6_mreq mreq;
+
+  mreq.ipv6mr_multiaddr = addr;
+  mreq.ipv6mr_interface = if_nametoindex(iface);
+
+  if (mreq.ipv6mr_interface == 0)
+    goto error;
+
+  if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
+    goto error;
+
+  return;
+
+error:
+  fprintf(stderr, "Could not join multicast group on %s: ", iface);
+  perror(NULL);
+  return;
+}
+
+#define REQUESTSIZE 64
+
+char *recvrequest(const int sock, struct sockaddr *client_addr, socklen_t *clilen) {
+  char request_buffer[REQUESTSIZE];
+  ssize_t read_bytes;
+
+  read_bytes = recvfrom(sock, request_buffer, sizeof(request_buffer), 0, client_addr, clilen);
+
+  if (read_bytes < 0) {
+    perror("recvfrom failed");
+    exit(EXIT_FAILURE);
+  }
+
+  char *request = strndup(request_buffer, read_bytes);
+
+  if (request == NULL)
+    perror("Could not receive request");
+
+  return strsep(&request, "\r\n\t ");
+}
+
+void serve(const int sock, const char *script) {
+  char *request;
+  socklen_t clilen;
+  struct sockaddr_in6 client_addr;
+
+  clilen = sizeof(client_addr);
+
+  while (1) {
+    request = recvrequest(sock, (struct sockaddr*)&client_addr, &clilen);
+
+    int cmp = strcmp(request, "nodeinfo");
+    free(request);
+
+    if (cmp != 0)
+      continue;
+
+    char *msg;
+    size_t msg_length;
+    msg = run_script(&msg_length, script);
+
+    if (sendto(sock, msg, msg_length, 0, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
+      perror("sendto failed");
+      exit(EXIT_FAILURE);
+    }
+
+    free(msg);
+  }
+}
+
+int main(int argc, char **argv) {
+  int sock;
+  struct sockaddr_in6 server_addr = {};
+  char *script = NULL;
+  struct in6_addr mgroup_addr;
+
+  sock = socket(PF_INET6, SOCK_DGRAM, 0);
+
+  if (sock < 0) {
+    perror("creating socket");
+    exit(EXIT_FAILURE);
+  }
+
+  server_addr.sin6_family = AF_INET6;
+  server_addr.sin6_addr = in6addr_any;
+
+  opterr = 0;
+
+  int group_set = 0;
+
+  int c;
+  while ((c = getopt(argc, argv, "p:g:s:i:h")) != -1)
+    switch (c) {
+      case 'p':
+        server_addr.sin6_port = htons(atoi(optarg));
+        break;
+      case 'g':
+        if (!inet_pton(AF_INET6, optarg, &mgroup_addr)) {
+          perror("Invalid multicast group. This message will probably confuse you");
+          exit(EXIT_FAILURE);
+        }
+
+        group_set = 1;
+        break;
+      case 's':
+        script = optarg;
+
+        break;
+      case 'i':
+        if (!group_set) {
+          fprintf(stderr, "Multicast group must be given before interface.\n");
+          exit(EXIT_FAILURE);
+        }
+        join_mcast(sock, mgroup_addr, optarg);
+        break;
+      case 'h':
+        usage();
+        exit(EXIT_SUCCESS);
+        break;
+      default:
+        fprintf(stderr, "Invalid parameter %c ignored.\n", c);
+    }
+
+  if (script == NULL) {
+    fprintf(stderr, "No script given\n");
+    exit(EXIT_FAILURE);
+  }
+
+  if (bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
+    perror("bind failed");
+    exit(EXIT_FAILURE);
+  }
+
+  serve(sock, script);
+
+  return EXIT_FAILURE;
+}
diff --git a/package/gluon-authorized-keys/Makefile b/package/gluon-authorized-keys/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..6ef90da31820355c525d837a996f4408a3829c28
--- /dev/null
+++ b/package/gluon-authorized-keys/Makefile
@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-authorized-keys
+PKG_VERSION:=2
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-authorized-keys
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Fill /etc/dropbear/authorized_keys from site.conf
+  DEPENDS:=+gluon-core
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-authorized-keys/install
+	$(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-authorized-keys/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-authorized-keys))
diff --git a/package/gluon-authorized-keys/check_site.lua b/package/gluon-authorized-keys/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..d1acfabe6dd4379e877e2be38af241860696e296
--- /dev/null
+++ b/package/gluon-authorized-keys/check_site.lua
@@ -0,0 +1 @@
+need_string_array 'authorized_keys'
diff --git a/package/gluon-authorized-keys/files/lib/gluon/upgrade/100-authorized-keys b/package/gluon-authorized-keys/files/lib/gluon/upgrade/100-authorized-keys
new file mode 100755
index 0000000000000000000000000000000000000000..643fa076ad5ae00807ecc1eb566b2da4a28839fb
--- /dev/null
+++ b/package/gluon-authorized-keys/files/lib/gluon/upgrade/100-authorized-keys
@@ -0,0 +1,22 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local file = '/etc/dropbear/authorized_keys'
+
+local keys = {}
+
+function load_keys()
+  for line in io.lines(file) do
+    keys[line] = true
+  end
+end
+
+pcall(load_keys)
+
+local f = io.open(file, 'a')
+for _, key in ipairs(site.authorized_keys) do
+  if not keys[key] then
+    f:write(key .. '\n')
+  end
+end
+f:close()
diff --git a/package/gluon-autoupdater/Makefile b/package/gluon-autoupdater/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..b33bada9626a56395cf7ddf98bf2a1e075e5edc2
--- /dev/null
+++ b/package/gluon-autoupdater/Makefile
@@ -0,0 +1,42 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-autoupdater
+PKG_VERSION:=4
+PKG_RELEASE:=$(GLUON_BRANCH)
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-autoupdater
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  DEPENDS:=+gluon-core +gluon-cron +autoupdater
+  TITLE:=Automatically update firmware
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-autoupdater/install
+	$(CP) ./files/* $(1)/
+
+	if [ '$(GLUON_BRANCH)' ]; then \
+		$(INSTALL_DIR) $(1)/lib/gluon/autoupdater; \
+		echo '$(GLUON_BRANCH)' > $(1)/lib/gluon/autoupdater/default_branch; \
+	fi
+endef
+
+define Package/gluon-autoupdater/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-autoupdater))
diff --git a/package/gluon-autoupdater/check_site.lua b/package/gluon-autoupdater/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..1d8996f05505ee46369ce52849b0c320063b5383
--- /dev/null
+++ b/package/gluon-autoupdater/check_site.lua
@@ -0,0 +1,12 @@
+need_string 'autoupdater.branch'
+
+local function check_branch(k, _)
+   local prefix = string.format('autoupdater.branches[%q].', k)
+
+   need_string(prefix .. 'name')
+   need_string_array(prefix .. 'mirrors')
+   need_number(prefix .. 'good_signatures')
+   need_string_array(prefix .. 'pubkeys')
+end
+
+need_table('autoupdater.branches', check_branch)
diff --git a/package/gluon-autoupdater/files/lib/gluon/announce/nodeinfo.d/software/autoupdater b/package/gluon-autoupdater/files/lib/gluon/announce/nodeinfo.d/software/autoupdater
new file mode 100644
index 0000000000000000000000000000000000000000..75a67578d84bcae991b1f34b4eff9053fe9e35c3
--- /dev/null
+++ b/package/gluon-autoupdater/files/lib/gluon/announce/nodeinfo.d/software/autoupdater
@@ -0,0 +1,7 @@
+local autoupdater = uci:get_all('autoupdater', 'settings')
+if autoupdater then
+	return {
+		branch = autoupdater['branch'],
+		enabled = uci:get_bool('autoupdater', 'settings', 'enabled'),
+	}
+end
diff --git a/package/gluon-autoupdater/files/lib/gluon/upgrade/500-autoupdater b/package/gluon-autoupdater/files/lib/gluon/upgrade/500-autoupdater
new file mode 100755
index 0000000000000000000000000000000000000000..fec313b3f2a3e7929247bb217c30f4ed75cc1ada
--- /dev/null
+++ b/package/gluon-autoupdater/files/lib/gluon/upgrade/500-autoupdater
@@ -0,0 +1,57 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+
+local c = uci.cursor()
+
+
+for name, config in pairs(site.autoupdater.branches) do
+	c:delete('autoupdater', name)
+	c:section('autoupdater', 'branch', name,
+		  {
+			  name = config.name,
+			  mirror = config.mirrors,
+			  good_signatures = config.good_signatures,
+			  pubkey = config.pubkeys,
+		  }
+	)
+end
+
+if not c:get('autoupdater', 'settings') then
+	local enabled = 0
+	local branch = site.autoupdater.branch
+
+	local f = io.open('/lib/gluon/autoupdater/default_branch')
+	if f then
+		enabled = 1
+		branch = f:read('*line')
+		f:close()
+	end
+
+	c:section('autoupdater', 'autoupdater', 'settings',
+		  {
+			  enabled = enabled,
+			  branch = branch,
+		  }
+	)
+end
+
+c:set('autoupdater', 'settings', 'version_file', '/lib/gluon/release')
+
+c:save('autoupdater')
+c:commit('autoupdater')
+
+
+local autoupdater_util = require 'autoupdater.util'
+autoupdater_util.randomseed()
+
+
+-- Perform updates at a random time between 04:00 and 05:00, and once an hour
+-- a fallback update (used after the regular updates haven't
+local minute = math.random(0, 59)
+
+local f = io.open('/lib/gluon/cron/autoupdater', 'w')
+f:write(string.format('%i 4 * * * /usr/sbin/autoupdater\n', minute))
+f:write(string.format('%i 0-3,5-23 * * * /usr/sbin/autoupdater --fallback\n', minute))
+f:close()
diff --git a/package/gluon-config-mode-autoupdater/Makefile b/package/gluon-config-mode-autoupdater/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d480e13222729d2c0cdc2f9dd9fd3ac95f486ba1
--- /dev/null
+++ b/package/gluon-config-mode-autoupdater/Makefile
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-autoupdater
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-autoupdater
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Let the user know whether the autoupdater is enabled or not.
+  DEPENDS:=+gluon-config-mode-core +gluon-autoupdater
+endef
+
+define Package/gluon-config-mode-autoupdater/description
+	Luci based config mode
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-autoupdater,i18n)
+endef
+
+define Package/gluon-config-mode-autoupdater/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-autoupdater,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-autoupdater))
diff --git a/package/gluon-config-mode-autoupdater/files/lib/gluon/config-mode/wizard/0050-autoupdater-info.lua b/package/gluon-config-mode-autoupdater/files/lib/gluon/config-mode/wizard/0050-autoupdater-info.lua
new file mode 100644
index 0000000000000000000000000000000000000000..3bbe0390ae01261cb250338e26d29a07fcd7eefd
--- /dev/null
+++ b/package/gluon-config-mode-autoupdater/files/lib/gluon/config-mode/wizard/0050-autoupdater-info.lua
@@ -0,0 +1,19 @@
+local cbi = require "luci.cbi"
+local i18n = require "luci.i18n"
+local uci = luci.model.uci.cursor()
+
+local M = {}
+
+function M.section(form)
+  local enabled = uci:get_bool("autoupdater", "settings", "enabled")
+  if enabled then
+    local s = form:section(cbi.SimpleSection, nil,
+      i18n.translate('This node will automatically update its firmware when a new version is available.'))
+  end
+end
+
+function M.handle(data)
+  return
+end
+
+return M
diff --git a/package/gluon-config-mode-autoupdater/i18n/de.po b/package/gluon-config-mode-autoupdater/i18n/de.po
new file mode 100644
index 0000000000000000000000000000000000000000..5a55e34986e66d755b425872a33e87510b670c0a
--- /dev/null
+++ b/package/gluon-config-mode-autoupdater/i18n/de.po
@@ -0,0 +1,17 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2015-03-18 16:03+0100\n"
+"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid ""
+"This node will automatically update its firmware when a new version is "
+"available."
+msgstr "Dieser Knoten aktualisiert seine Firmware automatisch, sobald "
+"eine neue Version vorliegt."
diff --git a/package/gluon-config-mode-autoupdater/i18n/gluon-config-mode-autoupdater.pot b/package/gluon-config-mode-autoupdater/i18n/gluon-config-mode-autoupdater.pot
new file mode 100644
index 0000000000000000000000000000000000000000..9e6272cd0ff5641b6000b5ccf837b146fa0fda14
--- /dev/null
+++ b/package/gluon-config-mode-autoupdater/i18n/gluon-config-mode-autoupdater.pot
@@ -0,0 +1,7 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid ""
+"This node will automatically update its firmware when a new version is "
+"available."
+msgstr ""
diff --git a/package/gluon-config-mode-contact-info/Makefile b/package/gluon-config-mode-contact-info/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d5c50dd6d2c493e2eda4011434338d1c4e5be9db
--- /dev/null
+++ b/package/gluon-config-mode-contact-info/Makefile
@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-contact-info
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-contact-info
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Set a custom string that will be distributed in the mesh.
+  DEPENDS:=+gluon-config-mode-core +gluon-node-info
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-contact-info,i18n)
+endef
+
+define Package/gluon-config-mode-contact-info/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-contact-info,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-contact-info))
diff --git a/package/gluon-config-mode-contact-info/files/lib/gluon/config-mode/wizard/0500-contact-info.lua b/package/gluon-config-mode-contact-info/files/lib/gluon/config-mode/wizard/0500-contact-info.lua
new file mode 100644
index 0000000000000000000000000000000000000000..a2182f95966d6608cfdd657c04383b020014c857
--- /dev/null
+++ b/package/gluon-config-mode-contact-info/files/lib/gluon/config-mode/wizard/0500-contact-info.lua
@@ -0,0 +1,34 @@
+local cbi = require "luci.cbi"
+local i18n = require "luci.i18n"
+local uci = luci.model.uci.cursor()
+
+local M = {}
+
+function M.section(form)
+  local s = form:section(cbi.SimpleSection, nil, i18n.translate(
+    'You can provide your contact information here to '
+      .. 'allow others to contact you. Please note that '
+      .. 'this information will be visible <em>publicly</em> '
+      .. 'on the internet together with your node\'s coordinates.'
+    )
+  )
+
+  local o = s:option(cbi.Value, "_contact", i18n.translate("Contact info"))
+  o.default = uci:get_first("gluon-node-info", "owner", "contact", "")
+  o.rmempty = true
+  o.datatype = "string"
+  o.description = i18n.translate("e.g. E-mail or phone number")
+  o.maxlen = 140
+end
+
+function M.handle(data)
+  if data._contact ~= nil then
+    uci:set("gluon-node-info", uci:get_first("gluon-node-info", "owner"), "contact", data._contact)
+  else
+    uci:delete("gluon-node-info", uci:get_first("gluon-node-info", "owner"), "contact")
+  end
+  uci:save("gluon-node-info")
+  uci:commit("gluon-node-info")
+end
+
+return M
diff --git a/package/gluon-config-mode-contact-info/i18n/de.po b/package/gluon-config-mode-contact-info/i18n/de.po
new file mode 100644
index 0000000000000000000000000000000000000000..719246f44724bf695a492bc7183b493dedc949c9
--- /dev/null
+++ b/package/gluon-config-mode-contact-info/i18n/de.po
@@ -0,0 +1,27 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2015-03-19 01:32+0100\n"
+"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "Contact info"
+msgstr "Kontakt"
+
+msgid ""
+"You can provide your contact information here to allow others to contact "
+"you. Please note that this information will be visible <em>publicly</em> on "
+"the internet together with your node's coordinates."
+msgstr ""
+"Hier kannst du einen <em>öffentlichen</em> Hinweis hinterlegen, um anderen "
+"zu ermöglichen, Kontakt mit dir aufzunehmen. Bitte beachte, dass "
+"dieser Hinweis auch öffentlich im Internet, zusammen mit den Koordinaten "
+"deines Knotens, einsehbar sein wird."
+
+msgid "e.g. E-mail or phone number"
+msgstr "z.B. E-Mail oder Telefonnummer"
diff --git a/package/gluon-config-mode-contact-info/i18n/gluon-config-mode-contact-info.pot b/package/gluon-config-mode-contact-info/i18n/gluon-config-mode-contact-info.pot
new file mode 100644
index 0000000000000000000000000000000000000000..63939a8384ada32a7512259d986f945467ff15f4
--- /dev/null
+++ b/package/gluon-config-mode-contact-info/i18n/gluon-config-mode-contact-info.pot
@@ -0,0 +1,14 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "Contact info"
+msgstr ""
+
+msgid ""
+"You can provide your contact information here to allow others to contact "
+"you. Please note that this information will be visible <em>publicly</em> on "
+"the internet together with your node's coordinates."
+msgstr ""
+
+msgid "e.g. E-mail or phone number"
+msgstr ""
diff --git a/package/gluon-config-mode-core/Makefile b/package/gluon-config-mode-core/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..35946b27d95bde0bedfa9627040790f9722c8e17
--- /dev/null
+++ b/package/gluon-config-mode-core/Makefile
@@ -0,0 +1,39 @@
+# Copyright (C) 2012 Nils Schneider <nils at nilsschneider.net>
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-core
+PKG_VERSION:=2
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-core
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Luci based config mode for user friendly setup of new mesh nodes
+  DEPENDS:=+gluon-setup-mode +gluon-luci-theme +gluon-lock-password $(GLUON_I18N_PACKAGES)
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-core,i18n)
+endef
+
+define Package/gluon-config-mode-core/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-core,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-core))
diff --git a/package/gluon-config-mode-core/files/lib/gluon/config-mode/reboot/0900-msg-reboot.lua b/package/gluon-config-mode-core/files/lib/gluon/config-mode/reboot/0900-msg-reboot.lua
new file mode 100644
index 0000000000000000000000000000000000000000..bf27c07f7a8248326fee832ddafb325675f3522f
--- /dev/null
+++ b/package/gluon-config-mode-core/files/lib/gluon/config-mode/reboot/0900-msg-reboot.lua
@@ -0,0 +1,3 @@
+local i18n = require 'luci.i18n'
+
+return function () luci.template.render_string(i18n.translate('gluon-config-mode:reboot')) end
diff --git a/package/gluon-config-mode-core/files/usr/lib/lua/luci/controller/gluon-config-mode/index.lua b/package/gluon-config-mode-core/files/usr/lib/lua/luci/controller/gluon-config-mode/index.lua
new file mode 100644
index 0000000000000000000000000000000000000000..8f55f54013114f068e4743c793f58537723b12dd
--- /dev/null
+++ b/package/gluon-config-mode-core/files/usr/lib/lua/luci/controller/gluon-config-mode/index.lua
@@ -0,0 +1,89 @@
+--[[
+Copyright 2013 Nils Schneider <nils@nilsschneider.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]--
+
+module("luci.controller.gluon-config-mode.index", package.seeall)
+
+function index()
+  local uci_state = luci.model.uci.cursor_state()
+
+  if uci_state:get_first("gluon-setup-mode", "setup_mode", "running", "0") == "1" then
+    local root = node()
+    if not root.target then
+      root.target = alias("gluon-config-mode")
+      root.index = true
+    end
+
+    page          = node()
+    page.lock     = true
+    page.target   = alias("gluon-config-mode")
+    page.subindex = true
+    page.index    = false
+
+    page          = node("gluon-config-mode")
+    page.title    = _("Wizard")
+    page.target   = alias("gluon-config-mode", "wizard")
+    page.order    = 5
+    page.setuser  = "root"
+    page.setgroup = "root"
+    page.index    = true
+
+    entry({"gluon-config-mode", "wizard"}, form("gluon-config-mode/wizard")).index = true
+    entry({"gluon-config-mode", "reboot"}, call("action_reboot"))
+  end
+end
+
+function action_reboot()
+  local uci = luci.model.uci.cursor()
+
+  uci:set("gluon-setup-mode", uci:get_first("gluon-setup-mode", "setup_mode"), "configured", "1")
+  uci:save("gluon-setup-mode")
+  uci:commit("gluon-setup-mode")
+
+  if nixio.fork() ~= 0 then
+    local fs = require "luci.fs"
+
+    local parts_dir = "/lib/gluon/config-mode/reboot/"
+    local files = fs.dir(parts_dir)
+
+    table.sort(files)
+
+    local parts = {}
+
+    for _, entry in ipairs(files) do
+      if entry:sub(1, 1) ~= '.' then
+        local f = dofile(parts_dir .. '/' .. entry)
+        if f ~= nil then
+          table.insert(parts, f)
+        end
+      end
+    end
+
+    local hostname = uci:get_first("system", "system", "hostname")
+
+    luci.template.render("gluon-config-mode/reboot", { parts=parts
+                                                     , hostname=hostname
+                                                     })
+  else
+    debug.setfenv(io.stdout, debug.getfenv(io.open '/dev/null'))
+    io.stdout:close()
+
+    -- Sleep a little so the browser can fetch everything required to
+    -- display the reboot page, then reboot the device.
+    nixio.nanosleep(2)
+
+    -- Run reboot with popen so it gets its own std filehandles.
+    io.popen("reboot")
+
+    -- Prevent any further execution in this child.
+    os.exit()
+  end
+end
diff --git a/package/gluon-config-mode-core/files/usr/lib/lua/luci/model/cbi/gluon-config-mode/wizard.lua b/package/gluon-config-mode-core/files/usr/lib/lua/luci/model/cbi/gluon-config-mode/wizard.lua
new file mode 100644
index 0000000000000000000000000000000000000000..706543b23f0ed484e3293f1318b59de597140e71
--- /dev/null
+++ b/package/gluon-config-mode-core/files/usr/lib/lua/luci/model/cbi/gluon-config-mode/wizard.lua
@@ -0,0 +1,38 @@
+local wizard_dir = "/lib/gluon/config-mode/wizard/"
+local i18n = luci.i18n
+local uci = luci.model.uci.cursor()
+local fs = require "luci.fs"
+local f, s
+
+local wizard = {}
+local files = fs.dir(wizard_dir)
+
+table.sort(files)
+
+for _, entry in ipairs(files) do
+  if entry:sub(1, 1) ~= '.' then
+    table.insert(wizard, dofile(wizard_dir .. '/' .. entry))
+  end
+end
+
+f = SimpleForm("wizard")
+f.reset = false
+f.template = "gluon-config-mode/cbi/wizard"
+
+for _, s in ipairs(wizard) do
+  s.section(f)
+end
+
+function f.handle(self, state, data)
+  if state == FORM_VALID then
+    for _, s in ipairs(wizard) do
+      s.handle(data)
+    end
+
+    luci.http.redirect(luci.dispatcher.build_url("gluon-config-mode", "reboot"))
+  end
+
+  return true
+end
+
+return f
diff --git a/package/gluon-config-mode-core/files/usr/lib/lua/luci/view/gluon-config-mode/cbi/wizard.htm b/package/gluon-config-mode-core/files/usr/lib/lua/luci/view/gluon-config-mode/cbi/wizard.htm
new file mode 100644
index 0000000000000000000000000000000000000000..d2210ecad0761d41d940cde811fd3604e18e476d
--- /dev/null
+++ b/package/gluon-config-mode-core/files/usr/lib/lua/luci/view/gluon-config-mode/cbi/wizard.htm
@@ -0,0 +1,46 @@
+<%-
+	local sysconfig = require 'gluon.sysconfig'
+	local i18n = require 'luci.i18n'
+	local template = require 'luci.template'
+-%>
+
+<h2><%:Welcome!%></h2>
+<p>
+	<%= template.render_string(i18n.translate('gluon-config-mode:welcome'), {hostname=hostname, sysconfig=sysconfig}) %>
+</p>
+
+<% if not self.embedded then %>
+<form method="post" enctype="multipart/form-data" action="<%=REQUEST_URI%>">
+	<div>
+		<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+		<input type="hidden" name="cbi.submit" value="1" />
+	</div>
+<% end %>
+	<div class="cbi-map" id="cbi-<%=self.config%>">
+		<% if self.title and #self.title > 0 then %><h2><a id="content" name="content"><%=self.title%></a></h2><% end %>
+		<% if self.description and #self.description > 0 then %><div class="cbi-map-descr"><%=self.description%></div><% end %>
+		<% self:render_children() %>
+		<br />
+	</div>
+<%- if self.message then %>
+	<div><%=self.message%></div>
+<%- end %>
+<%- if self.errmessage then %>
+	<div class="error"><%=self.errmessage%></div>
+<%- end %>
+<% if not self.embedded then %>
+	<div class="cbi-page-actions">
+<%-
+		if type(self.hidden) == "table" then
+		  for k, v in pairs(self.hidden) do
+-%>
+		<input type="hidden" id="<%=k%>" name="<%=k%>" value="<%=pcdata(v)%>" />
+<%-
+		  end
+		end
+%>
+		<input class="cbi-button cbi-button-save" type="submit" value="<%:Save & restart%>" />
+		<script type="text/javascript">cbi_d_update();</script>
+	</div>
+</form>
+<% end %>
diff --git a/package/gluon-config-mode-core/files/usr/lib/lua/luci/view/gluon-config-mode/reboot.htm b/package/gluon-config-mode-core/files/usr/lib/lua/luci/view/gluon-config-mode/reboot.htm
new file mode 100644
index 0000000000000000000000000000000000000000..e8f32d995e4c325cf291584d0011a73c694cbe95
--- /dev/null
+++ b/package/gluon-config-mode-core/files/usr/lib/lua/luci/view/gluon-config-mode/reboot.htm
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%=luci.i18n.context.lang%>" lang="<%=luci.i18n.context.lang%>">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <title><%=hostname%> is rebooting</title>
+    <link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />
+  </head>
+  <body>
+    <div id="maincontainer">
+      <div id="maincontent">
+        <h2><%:Your node's setup is now complete.%></h2>
+        <% for k, v in ipairs(parts) do v() end %>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/package/gluon-config-mode-core/i18n/de.po b/package/gluon-config-mode-core/i18n/de.po
new file mode 100644
index 0000000000000000000000000000000000000000..58d238942faa8154f1c7a1772e5a4c286f22f474
--- /dev/null
+++ b/package/gluon-config-mode-core/i18n/de.po
@@ -0,0 +1,24 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2015-03-19 02:07+0100\n"
+"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#, fuzzy
+msgid "Save & restart"
+msgstr "Speichern & Neustarten"
+
+msgid "Welcome!"
+msgstr "Willkommen!"
+
+msgid "Wizard"
+msgstr "Wizard"
+
+msgid "Your node's setup is now complete."
+msgstr "Dein Knoten ist nun fertig eingerichtet."
diff --git a/package/gluon-config-mode-core/i18n/gluon-config-mode-core.pot b/package/gluon-config-mode-core/i18n/gluon-config-mode-core.pot
new file mode 100644
index 0000000000000000000000000000000000000000..a80d3b9736148ea1d1c47af82879864725c6186e
--- /dev/null
+++ b/package/gluon-config-mode-core/i18n/gluon-config-mode-core.pot
@@ -0,0 +1,14 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "Save & restart"
+msgstr ""
+
+msgid "Welcome!"
+msgstr ""
+
+msgid "Wizard"
+msgstr ""
+
+msgid "Your node's setup is now complete."
+msgstr ""
diff --git a/package/gluon-config-mode-geo-location/Makefile b/package/gluon-config-mode-geo-location/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4b5204e0fbe0c10ee5f517790a1009c2d5cb4664
--- /dev/null
+++ b/package/gluon-config-mode-geo-location/Makefile
@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-geo-location
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-geo-location
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Set geographic location of a node
+  DEPENDS:=+gluon-config-mode-core +gluon-node-info
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-geo-location,i18n)
+endef
+
+define Package/gluon-config-mode-geo-location/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-geo-location,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-geo-location))
diff --git a/package/gluon-config-mode-geo-location/files/lib/gluon/config-mode/wizard/0400-geo-location.lua b/package/gluon-config-mode-geo-location/files/lib/gluon/config-mode/wizard/0400-geo-location.lua
new file mode 100644
index 0000000000000000000000000000000000000000..a3ac089d4110d57b8af20df84fcc9b867ec176fb
--- /dev/null
+++ b/package/gluon-config-mode-geo-location/files/lib/gluon/config-mode/wizard/0400-geo-location.lua
@@ -0,0 +1,60 @@
+local cbi = require "luci.cbi"
+local i18n = require "luci.i18n"
+local uci = luci.model.uci.cursor()
+
+local M = {}
+
+function M.section(form)
+  local s = form:section(cbi.SimpleSection, nil, i18n.translate(
+    'If you want the location of your node to be displayed on the map, '
+      .. 'you can enter its coordinates here. Specifying the altitude '
+      .. 'is optional and should only be done if a proper value is known.'))
+
+
+  local o
+
+  o = s:option(cbi.Flag, "_location", i18n.translate("Show node on the map"))
+  o.default = uci:get_first("gluon-node-info", "location", "share_location", o.disabled)
+  o.rmempty = false
+
+  o = s:option(cbi.Value, "_latitude", i18n.translate("Latitude"))
+  o.default = uci:get_first("gluon-node-info", "location", "latitude")
+  o:depends("_location", "1")
+  o.rmempty = false
+  o.datatype = "float"
+  o.description = i18n.translatef("e.g. %s", "53.873621")
+
+  o = s:option(cbi.Value, "_longitude", i18n.translate("Longitude"))
+  o.default = uci:get_first("gluon-node-info", "location", "longitude")
+  o:depends("_location", "1")
+  o.rmempty = false
+  o.datatype = "float"
+  o.description = i18n.translatef("e.g. %s", "10.689901")
+
+  o = s:option(cbi.Value, "_altitude", i18n.translate("Altitude"))
+  o.default = uci:get_first("gluon-node-info", "location", "altitude")
+  o:depends("_location", "1")
+  o.rmempty = true
+  o.datatype = "float"
+  o.description = i18n.translatef("e.g. %s", "11.51")
+
+end
+
+function M.handle(data)
+  local sname = uci:get_first("gluon-node-info", "location")
+
+  uci:set("gluon-node-info", sname, "share_location", data._location)
+  if data._location and data._latitude ~= nil and data._longitude ~= nil then
+    uci:set("gluon-node-info", sname, "latitude", data._latitude)
+    uci:set("gluon-node-info", sname, "longitude", data._longitude)
+    if data._altitude ~= nil then
+      uci:set("gluon-node-info", sname, "altitude", data._altitude)
+    else
+      uci:delete("gluon-node-info", sname, "altitude")
+    end
+  end
+  uci:save("gluon-node-info")
+  uci:commit("gluon-node-info")
+end
+
+return M
diff --git a/package/gluon-config-mode-geo-location/i18n/de.po b/package/gluon-config-mode-geo-location/i18n/de.po
new file mode 100644
index 0000000000000000000000000000000000000000..e83443dd0b5299549f1a557bcc0138120858a9d7
--- /dev/null
+++ b/package/gluon-config-mode-geo-location/i18n/de.po
@@ -0,0 +1,36 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gluon-config-mode-geo-location\n"
+"PO-Revision-Date: 2015-03-23 02:18+0100\n"
+"Last-Translator: Martin Weinelt <martin@darmstadt.freifunk.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid ""
+"If you want the location of your node to be displayed on the map, you can "
+"enter its coordinates here. Specifying the altitude is optional and should "
+"only be done if a proper value is known."
+msgstr ""
+"Um deinen Knoten auf der Karte anzeigen zu können, benötigen wir seine "
+"Koordinaten. Hier hast du die Möglichkeit, diese zu hinterlegen. Die "
+"Höhenangabe ist optional und sollte nur gesetzt werden, wenn ein exakter "
+"Wert bekannt ist."
+
+msgid "Latitude"
+msgstr "Breitengrad"
+
+msgid "Longitude"
+msgstr "Längengrad"
+
+msgid "Altitude"
+msgstr "Höhenmeter über Normalnull"
+
+msgid "Show node on the map"
+msgstr "Knoten auf der Karte anzeigen"
+
+msgid "e.g. %s"
+msgstr "z.B. %s"
diff --git a/package/gluon-config-mode-geo-location/i18n/gluon-config-mode-geo-location.pot b/package/gluon-config-mode-geo-location/i18n/gluon-config-mode-geo-location.pot
new file mode 100644
index 0000000000000000000000000000000000000000..84800c8a12de214a15a5c3aea68be3cae6f0e525
--- /dev/null
+++ b/package/gluon-config-mode-geo-location/i18n/gluon-config-mode-geo-location.pot
@@ -0,0 +1,20 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid ""
+"If you want the location of your node to be displayed on the map, you can "
+"enter its coordinates here. Specifying the altitude is optional and should "
+"only be done if a proper value is known."
+msgstr ""
+
+msgid "Latitude"
+msgstr ""
+
+msgid "Longitude"
+msgstr ""
+
+msgid "Show node on the map"
+msgstr ""
+
+msgid "e.g. %s"
+msgstr ""
diff --git a/package/gluon-config-mode-hostname/Makefile b/package/gluon-config-mode-hostname/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..255740e48d12f2a4c0df189e4251f55e97f647af
--- /dev/null
+++ b/package/gluon-config-mode-hostname/Makefile
@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-hostname
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-hostname
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Set the hostname
+  DEPENDS:=+gluon-config-mode-core
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-hostname,i18n)
+endef
+
+define Package/gluon-config-mode-hostname/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-hostname,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-hostname))
diff --git a/package/gluon-config-mode-hostname/files/lib/gluon/config-mode/wizard/0100-hostname.lua b/package/gluon-config-mode-hostname/files/lib/gluon/config-mode/wizard/0100-hostname.lua
new file mode 100644
index 0000000000000000000000000000000000000000..cf8bbf89188a923d834e2e8c35769c56dc2a0d5d
--- /dev/null
+++ b/package/gluon-config-mode-hostname/files/lib/gluon/config-mode/wizard/0100-hostname.lua
@@ -0,0 +1,21 @@
+local cbi = require "luci.cbi"
+local i18n = require "luci.i18n"
+local uci = luci.model.uci.cursor()
+
+local M = {}
+
+function M.section(form)
+  local s = form:section(cbi.SimpleSection, nil, nil)
+  local o = s:option(cbi.Value, "_hostname", i18n.translate("Node name"))
+  o.value = uci:get_first("system", "system", "hostname")
+  o.rmempty = false
+  o.datatype = "hostname"
+end
+
+function M.handle(data)
+  uci:set("system", uci:get_first("system", "system"), "hostname", data._hostname)
+  uci:save("system")
+  uci:commit("system")
+end
+
+return M
diff --git a/package/gluon-config-mode-hostname/i18n/de.po b/package/gluon-config-mode-hostname/i18n/de.po
new file mode 100644
index 0000000000000000000000000000000000000000..f1d2a7657b93f0162fe7d8bafd97ea3ddf0e2893
--- /dev/null
+++ b/package/gluon-config-mode-hostname/i18n/de.po
@@ -0,0 +1,14 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2015-03-19 00:54+0100\n"
+"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "Node name"
+msgstr "Name dieses Knotens"
diff --git a/package/gluon-config-mode-hostname/i18n/gluon-config-mode-hostname.pot b/package/gluon-config-mode-hostname/i18n/gluon-config-mode-hostname.pot
new file mode 100644
index 0000000000000000000000000000000000000000..dbaafc0bdf384ca531f46d4b51fa4d65c4e091d2
--- /dev/null
+++ b/package/gluon-config-mode-hostname/i18n/gluon-config-mode-hostname.pot
@@ -0,0 +1,5 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "Node name"
+msgstr ""
diff --git a/package/gluon-config-mode-mesh-vpn/Makefile b/package/gluon-config-mode-mesh-vpn/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4792e3d328ee2c68af4d447cc19961d4cd3f36dc
--- /dev/null
+++ b/package/gluon-config-mode-mesh-vpn/Makefile
@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-mesh-vpn
+PKG_VERSION:=2
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-mesh-vpn
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Toggle mesh-vpn and bandwidth limit
+  DEPENDS:=+gluon-config-mode-core +gluon-mesh-vpn-fastd +gluon-simple-tc
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-mesh-vpn,i18n)
+endef
+
+define Package/gluon-config-mode-mesh-vpn/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-mesh-vpn,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-mesh-vpn))
diff --git a/package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/reboot/0100-mesh-vpn.lua b/package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/reboot/0100-mesh-vpn.lua
new file mode 100644
index 0000000000000000000000000000000000000000..1b32c73114e733fb8277c99057a7d5c7e3001764
--- /dev/null
+++ b/package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/reboot/0100-mesh-vpn.lua
@@ -0,0 +1,29 @@
+local uci = luci.model.uci.cursor()
+local meshvpn_enabled = uci:get("fastd", "mesh_vpn", "enabled", "0")
+
+if meshvpn_enabled ~= "1" then
+  return nil
+else
+  local i18n = require "luci.i18n"
+  local util = require "luci.util"
+  local site = require 'gluon.site_config'
+  local sysconfig = require 'gluon.sysconfig'
+
+  local pubkey = util.trim(util.exec("/etc/init.d/fastd show_key " .. "mesh_vpn"))
+  local hostname = uci:get_first("system", "system", "hostname")
+
+  local msg = [[<p>]] .. i18n.translate('gluon-config-mode:pubkey') .. [[</p>
+               <div class="the-key">
+                 # <%= hostname %>
+                 <br/>
+               <%= pubkey %>
+               </div>]]
+
+  return function ()
+           luci.template.render_string(msg, { pubkey=pubkey
+                                            , hostname=hostname
+                                            , site=site
+                                            , sysconfig=sysconfig
+                                            })
+         end
+end
diff --git a/package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua b/package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua
new file mode 100644
index 0000000000000000000000000000000000000000..669a7bc34fe9f2724d7953a4214771cb82f6a0d3
--- /dev/null
+++ b/package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua
@@ -0,0 +1,64 @@
+local cbi = require "luci.cbi"
+local i18n = require "luci.i18n"
+local uci = luci.model.uci.cursor()
+
+local M = {}
+
+function M.section(form)
+  local msg = i18n.translate('Your internet connection can be used to establish an ' ..
+                             'encrypted connection with other nodes. ' ..
+                             'Enable this option if there are no other nodes reachable ' ..
+                             'over WLAN in your vicinity or you want to make a part of ' ..
+                             'your connection\'s bandwidth available for the network. You can limit how ' ..
+                             'much bandwidth the node will use at most.')
+  local s = form:section(cbi.SimpleSection, nil, msg)
+
+  local o
+
+  o = s:option(cbi.Flag, "_meshvpn", i18n.translate("Use internet connection (mesh VPN)"))
+  o.default = uci:get_bool("fastd", "mesh_vpn", "enabled") and o.enabled or o.disabled
+  o.rmempty = false
+
+  o = s:option(cbi.Flag, "_limit_enabled", i18n.translate("Limit bandwidth"))
+  o:depends("_meshvpn", "1")
+  o.default = uci:get_bool("gluon-simple-tc", "mesh_vpn", "enabled") and o.enabled or o.disabled
+  o.rmempty = false
+
+  o = s:option(cbi.Value, "_limit_ingress", i18n.translate("Downstream (kbit/s)"))
+  o:depends("_limit_enabled", "1")
+  o.value = uci:get("gluon-simple-tc", "mesh_vpn", "limit_ingress")
+  o.rmempty = false
+  o.datatype = "integer"
+
+  o = s:option(cbi.Value, "_limit_egress", i18n.translate("Upstream (kbit/s)"))
+  o:depends("_limit_enabled", "1")
+  o.value = uci:get("gluon-simple-tc", "mesh_vpn", "limit_egress")
+  o.rmempty = false
+  o.datatype = "integer"
+end
+
+function M.handle(data)
+  uci:set("fastd", "mesh_vpn", "enabled", data._meshvpn)
+  uci:save("fastd")
+  uci:commit("fastd")
+
+  -- checks for nil needed due to o:depends(...)
+  if data._limit_enabled ~= nil then
+    uci:set("gluon-simple-tc", "mesh_vpn", "interface")
+    uci:set("gluon-simple-tc", "mesh_vpn", "enabled", data._limit_enabled)
+    uci:set("gluon-simple-tc", "mesh_vpn", "ifname", "mesh-vpn")
+
+    if data._limit_ingress ~= nil then
+      uci:set("gluon-simple-tc", "mesh_vpn", "limit_ingress", data._limit_ingress)
+    end
+
+    if data._limit_egress ~= nil then
+      uci:set("gluon-simple-tc", "mesh_vpn", "limit_egress", data._limit_egress)
+    end
+
+    uci:commit("gluon-simple-tc")
+    uci:commit("gluon-simple-tc")
+  end
+end
+
+return M
diff --git a/package/gluon-config-mode-mesh-vpn/i18n/de.po b/package/gluon-config-mode-mesh-vpn/i18n/de.po
new file mode 100644
index 0000000000000000000000000000000000000000..8c613aa0c04c522b27fe51de511f13de56146de2
--- /dev/null
+++ b/package/gluon-config-mode-mesh-vpn/i18n/de.po
@@ -0,0 +1,36 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2015-03-19 22:05+0100\n"
+"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "Downstream (kbit/s)"
+msgstr "Downstream (kbit/s)"
+
+msgid "Limit bandwidth"
+msgstr "Bandbreite begrenzen"
+
+msgid "Upstream (kbit/s)"
+msgstr "Upstream (kbit/s)"
+
+msgid "Use internet connection (mesh VPN)"
+msgstr "Internetverbindung nutzen (Mesh-VPN)"
+
+msgid ""
+"Your internet connection can be used to establish an encrypted connection "
+"with other nodes. Enable this option if there are no other nodes reachable "
+"over WLAN in your vicinity or you want to make a part of your connection's "
+"bandwidth available for the network. You can limit how much bandwidth the "
+"node will use at most."
+msgstr ""
+"Dein Knoten kann deine Internetverbindung nutzen um darüber eine "
+"verschlüsselte Verbindung zu anderen Knoten aufzubauen. Die dafür "
+"genutzte Bandbreite kannst du beschränken. Aktiviere die Option, falls keine "
+"per WLAN erreichbaren Nachbarknoten in deiner Nähe sind oder du deine "
+"Internetverbindung für das Mesh-Netzwerk zur Verfügung stellen möchtest."
diff --git a/package/gluon-config-mode-mesh-vpn/i18n/gluon-config-mode-mesh-vpn.pot b/package/gluon-config-mode-mesh-vpn/i18n/gluon-config-mode-mesh-vpn.pot
new file mode 100644
index 0000000000000000000000000000000000000000..52e2eef8acebab13800df56ff7f487b3647733b4
--- /dev/null
+++ b/package/gluon-config-mode-mesh-vpn/i18n/gluon-config-mode-mesh-vpn.pot
@@ -0,0 +1,22 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "Downstream (kbit/s)"
+msgstr ""
+
+msgid "Limit bandwidth"
+msgstr ""
+
+msgid "Upstream (kbit/s)"
+msgstr ""
+
+msgid "Use internet connection (mesh VPN)"
+msgstr ""
+
+msgid ""
+"Your internet connection can be used to establish an encrypted connection "
+"with other nodes. Enable this option if there are no other nodes reachable "
+"over WLAN in your vicinity or you want to make a part of your connection's "
+"bandwidth available for the network. You can limit how much bandwidth the "
+"node will use at most."
+msgstr ""
diff --git a/package/gluon-core/Makefile b/package/gluon-core/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..2c632a0014573986ac3cc41ed51f75894c7212a6
--- /dev/null
+++ b/package/gluon-core/Makefile
@@ -0,0 +1,59 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-core
+PKG_VERSION:=3
+PKG_RELEASE:=$(GLUON_VERSION)
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-core
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Base files of Gluon
+  DEPENDS:=+gluon-site +lua-platform-info +luci-lib-nixio +odhcp6c +firewall
+endef
+
+
+define LangConfig
+config GLUON_LANG_$(1)
+	bool "$(GLUON_LANG_$(1)) language support"
+	depends on PACKAGE_gluon-core
+	default n
+
+endef
+
+
+define Package/gluon-core/config
+$(foreach lang,$(GLUON_SUPPORTED_LANGS),$(call LangConfig,$(lang)))
+endef
+
+
+define Package/gluon-core/description
+	Gluon community wifi mesh firmware framework: core
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-core/install
+	$(CP) ./files/* $(1)/
+
+	$(INSTALL_DIR) $(1)/lib/gluon
+	echo "$(GLUON_VERSION)" > $(1)/lib/gluon/gluon-version
+endef
+
+define Package/gluon-core/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-core))
diff --git a/package/gluon-core/check_site.lua b/package/gluon-core/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..1a6987a00fdabd755d2b2800740788601061537e
--- /dev/null
+++ b/package/gluon-core/check_site.lua
@@ -0,0 +1,10 @@
+need_string 'site_code'
+need_string 'site_name'
+
+need_string('hostname_prefix', false)
+need_string 'timezone'
+
+need_string_array('ntp_servers', false)
+
+need_string_match('prefix4', '^%d+.%d+.%d+.%d+/%d+$')
+need_string_match('prefix6', '^[%x:]+/%d+$')
diff --git a/package/gluon-core/files/etc/uci-defaults/zzz-gluon-upgrade b/package/gluon-core/files/etc/uci-defaults/zzz-gluon-upgrade
new file mode 100755
index 0000000000000000000000000000000000000000..a12ce78eb85e9a12ac1d883e82c0039a3a86dd86
--- /dev/null
+++ b/package/gluon-core/files/etc/uci-defaults/zzz-gluon-upgrade
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+for script in /lib/gluon/upgrade/*; do
+	"$script"
+done
diff --git a/package/gluon-core/files/lib/gluon/core/sysconfig/.keep b/package/gluon-core/files/lib/gluon/core/sysconfig/.keep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/package/gluon-core/files/lib/gluon/upgrade/001-upgrade b/package/gluon-core/files/lib/gluon/upgrade/001-upgrade
new file mode 100755
index 0000000000000000000000000000000000000000..6caba148b5c84660536cd4f66d86908f02c8e63a
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/001-upgrade
@@ -0,0 +1,10 @@
+#!/usr/bin/lua
+
+local fs = require 'luci.fs'
+local sysconfig = require 'gluon.sysconfig'
+
+
+if fs.isfile('/lib/gluon/version/core') and not sysconfig.gluon_version then
+  -- This isn't an initial upgrade, so set gluon_version
+  sysconfig.gluon_version = ''
+end
diff --git a/package/gluon-core/files/lib/gluon/upgrade/010-primary-mac b/package/gluon-core/files/lib/gluon/upgrade/010-primary-mac
new file mode 100755
index 0000000000000000000000000000000000000000..70aee39d32ee727f0e75129dd9bacb85a4e8888e
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/010-primary-mac
@@ -0,0 +1,42 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+
+
+if sysconfig.primary_mac then
+  os.exit(0)
+end
+
+
+local platform = require 'gluon.platform'
+
+local fs = require 'luci.fs'
+local util = require 'luci.util'
+
+
+local try_files = {
+  '/sys/class/ieee80211/phy0/macaddress',
+  '/sys/class/net/eth0/address',
+}
+
+if platform.match('ar71xx', 'generic', {'tl-wdr3600', 'tl-wdr4300'}) then
+  table.insert(try_files, 1, '/sys/class/ieee80211/phy1/macaddress')
+end
+
+if platform.match('ar71xx', 'generic', {'unifi-outdoor-plus'}) then
+  table.insert(try_files, 1, '/sys/class/net/eth0/address')
+end
+
+if platform.match('ar71xx', 'generic', {'archer-c5', 'archer-c7'}) then
+  table.insert(try_files, 1, '/sys/class/net/eth1/address')
+end
+
+
+for _, file in ipairs(try_files) do
+  local addr = fs.readfile(file)
+
+  if addr then
+    sysconfig.primary_mac = util.trim(addr)
+    break
+  end
+end
diff --git a/package/gluon-core/files/lib/gluon/upgrade/020-interfaces b/package/gluon-core/files/lib/gluon/upgrade/020-interfaces
new file mode 100755
index 0000000000000000000000000000000000000000..a051c738d42500f3239bb3b02bcccb02c374d19b
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/020-interfaces
@@ -0,0 +1,36 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+local gluon_util = require 'gluon.util'
+local platform = require 'gluon.platform'
+
+local uci = require('luci.model.uci').cursor()
+
+
+if not (sysconfig.lan_ifname or sysconfig.wan_ifname) then
+  local function iface_exists(name)
+    return (gluon_util.exec('ip', 'link', 'show', 'dev', (name:gsub('%..*$', ''))) == 0)
+  end
+
+
+  local lan_ifname = uci:get('network', 'lan', 'ifname')
+  local wan_ifname = uci:get('network', 'wan', 'ifname')
+
+  if platform.match('ar71xx', 'generic', {'cpe510', 'nanostation-m', 'nanostation-m-xw', 'unifi-outdoor-plus'}) then
+    lan_ifname, wan_ifname = wan_ifname, lan_ifname
+  end
+
+  if wan_ifname and iface_exists(wan_ifname) then
+    sysconfig.wan_ifname = wan_ifname
+    sysconfig.lan_ifname = lan_ifname
+  else
+    sysconfig.wan_ifname = lan_ifname
+  end
+
+
+  uci:delete('network', 'lan')
+  uci:delete('network', 'wan')
+
+  uci:save('network')
+  uci:commit('network')
+end
diff --git a/package/gluon-core/files/lib/gluon/upgrade/030-system b/package/gluon-core/files/lib/gluon/upgrade/030-system
new file mode 100755
index 0000000000000000000000000000000000000000..d7a6660507546a78eb6706e3388229cbf494f421
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/030-system
@@ -0,0 +1,18 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+
+-- Initial
+if not sysconfig.gluon_version then
+  local site = require 'gluon.site_config'
+  local util = require 'gluon.util'
+  local uci = require('luci.model.uci').cursor()
+
+  local system = uci:get_first('system', 'system')
+
+  uci:set('system', system, 'hostname', (site.hostname_prefix or '') .. util.node_id())
+  uci:set('system', system, 'timezone', site.timezone)
+
+  uci:save('system')
+  uci:commit('system')
+end
diff --git a/package/gluon-core/files/lib/gluon/upgrade/100-dnsmasq b/package/gluon-core/files/lib/gluon/upgrade/100-dnsmasq
new file mode 100755
index 0000000000000000000000000000000000000000..3636fbf6b2f03894d18b97e75bf081625851bd72
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/100-dnsmasq
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ -e /etc/dnsmasq.conf ]; then
+	sed -i -e '/^conf-dir=\/lib\/gluon\/dnsmasq\.d$/d' -e '/^conf-dir=\/var\/gluon\/dnsmasq\.d$/d' /etc/dnsmasq.conf
+fi
diff --git a/package/gluon-core/files/lib/gluon/upgrade/110-network b/package/gluon-core/files/lib/gluon/upgrade/110-network
new file mode 100755
index 0000000000000000000000000000000000000000..1fd78f3c7a547575dbe3bda684c679a3432bace0
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/110-network
@@ -0,0 +1,58 @@
+#!/usr/bin/lua
+
+local uci = require('luci.model.uci').cursor()
+local sysctl = require 'gluon.sysctl'
+local sysconfig = require 'gluon.sysconfig'
+
+
+uci:section('network', 'interface', 'wan',
+	    {
+	      ifname = sysconfig.wan_ifname,
+	      type = 'bridge',
+	      peerdns = 0,
+	      auto = 1,
+	    }
+)
+
+if not uci:get('network', 'wan', 'proto') then
+  uci:set('network', 'wan', 'proto', 'dhcp')
+end
+
+
+uci:section('network', 'interface', 'wan6',
+	    {
+	      ifname = 'br-wan',
+	      peerdns = 0,
+	      ip6table = 1,
+	    }
+)
+
+if not uci:get('network', 'wan6', 'proto') then
+  uci:set('network', 'wan6', 'proto', 'dhcpv6')
+end
+
+
+uci:section('network', 'rule6', 'wan6_lookup',
+	    {
+	       mark = '0x01/0x01',
+	       lookup = 1,
+	    }
+)
+
+uci:section('network', 'route6', 'wan6_unreachable',
+	    {
+	       type = 'unreachable',
+	       interface = 'loopback',
+	       target = '::/0',
+	       gateway = '::',
+	       table = 1,
+	       metric = 65535,
+	    }
+)
+
+uci:save('network')
+uci:commit('network')
+
+
+sysctl.set('net.ipv6.conf.all.accept_ra', 0)
+sysctl.set('net.ipv6.conf.default.accept_ra', 0)
diff --git a/package/gluon-core/files/lib/gluon/upgrade/120-ntp-servers b/package/gluon-core/files/lib/gluon/upgrade/120-ntp-servers
new file mode 100755
index 0000000000000000000000000000000000000000..2b3a2df6135e809a634fa26736a9e9369ca21acb
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/120-ntp-servers
@@ -0,0 +1,14 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+
+if not site.ntp_servers or #site.ntp_servers == 0 then
+	os.exit(0)
+end
+
+local c = uci.cursor()
+c:delete('system', 'ntp', 'server')
+c:set_list('system', 'ntp', 'server', site.ntp_servers)
+c:save('system')
+c:commit('system')
diff --git a/package/gluon-core/files/lib/gluon/upgrade/130-reboot-on-oom b/package/gluon-core/files/lib/gluon/upgrade/130-reboot-on-oom
new file mode 100755
index 0000000000000000000000000000000000000000..48cfc5a8f38ee50e176ec6e6cd2ba34ccb1633ab
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/130-reboot-on-oom
@@ -0,0 +1,5 @@
+#!/usr/bin/lua
+
+local sysctl = require 'gluon.sysctl'
+
+sysctl.set('vm.panic_on_oom', 1)
diff --git a/package/gluon-core/files/lib/gluon/upgrade/140-firewall-rules b/package/gluon-core/files/lib/gluon/upgrade/140-firewall-rules
new file mode 100755
index 0000000000000000000000000000000000000000..792e06a2c08337f2ecc2fc93596ce87acfceee37
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/140-firewall-rules
@@ -0,0 +1,30 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+
+local c = uci.cursor()
+
+
+local function reject_input_on_wan(zone)
+	if zone.name == 'wan' then
+		c:set('firewall', zone['.name'], 'input', 'REJECT')
+		c:set('firewall', zone['.name'], 'conntrack', '1')
+	end
+
+	return true
+end
+c:foreach('firewall', 'zone', reject_input_on_wan)
+
+c:section('firewall', 'rule', 'wan_ssh',
+	  {
+		  name = 'wan_ssh',
+		  src = 'wan',
+		  dest_port = '22',
+		  proto = 'tcp',
+		  target = 'ACCEPT',
+	  }
+)
+
+c:save('firewall')
+c:commit('firewall')
diff --git a/package/gluon-core/files/lib/gluon/upgrade/200-wireless b/package/gluon-core/files/lib/gluon/upgrade/200-wireless
new file mode 100755
index 0000000000000000000000000000000000000000..219e505d3e67191a368dad1344aa29aef3c6bef3
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/200-wireless
@@ -0,0 +1,12 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+
+-- Initial
+if not sysconfig.gluon_version then
+  local uci = require('luci.model.uci').cursor()
+
+  uci:delete_all('wireless', 'wifi-iface')
+  uci:save('wireless')
+  uci:commit('wireless')
+end
diff --git a/package/gluon-core/files/lib/gluon/upgrade/999-version b/package/gluon-core/files/lib/gluon/upgrade/999-version
new file mode 100755
index 0000000000000000000000000000000000000000..62f082069c22e8c356a272339a426a84bfc133f1
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/999-version
@@ -0,0 +1,11 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+
+local fs = require 'luci.fs'
+local util = require 'luci.util'
+
+
+-- Save the Gluon version in the sysconfig so we know which version we
+-- upgraded from after the next upgrade
+sysconfig.gluon_version = util.trim(fs.readfile('/lib/gluon/gluon-version'))
diff --git a/package/gluon-core/files/lib/upgrade/keep.d/gluon b/package/gluon-core/files/lib/upgrade/keep.d/gluon
new file mode 100644
index 0000000000000000000000000000000000000000..bc82c775aa5702a4247c671430f0a1e6ba8bd7e7
--- /dev/null
+++ b/package/gluon-core/files/lib/upgrade/keep.d/gluon
@@ -0,0 +1 @@
+/lib/gluon/core/sysconfig/
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/platform.lua b/package/gluon-core/files/usr/lib/lua/gluon/platform.lua
new file mode 100644
index 0000000000000000000000000000000000000000..3d56f081bbad199fadf346b3db2794687cf80420
--- /dev/null
+++ b/package/gluon-core/files/usr/lib/lua/gluon/platform.lua
@@ -0,0 +1,31 @@
+local platform_info = require 'platform_info'
+local util = require 'luci.util'
+
+local setmetatable = setmetatable
+
+
+module 'gluon.platform'
+
+setmetatable(_M,
+	     {
+		__index = platform_info,
+	     }
+)
+
+function match(target, subtarget, boards)
+   if get_target() ~= target then
+      return false
+   end
+
+   if get_subtarget() ~= subtarget then
+      return false
+   end
+
+   if not util.contains(boards, get_board_name()) then
+      return false
+   end
+
+   return true
+end
+
+
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/site_config.lua b/package/gluon-core/files/usr/lib/lua/gluon/site_config.lua
new file mode 100644
index 0000000000000000000000000000000000000000..cf15148385ad78f26c67c9095ea6c8aba05e30b1
--- /dev/null
+++ b/package/gluon-core/files/usr/lib/lua/gluon/site_config.lua
@@ -0,0 +1,21 @@
+local config = os.getenv('GLUON_SITE_CONFIG') or '/lib/gluon/site.conf'
+
+local function loader()
+   coroutine.yield('return ')
+   coroutine.yield(io.open(config):read('*a'))
+end
+
+-- setfenv doesn't work with Lua 5.2 anymore, but we're using 5.1
+local site_config = setfenv(assert(load(coroutine.wrap(loader), 'site.conf')), {})()
+
+local setmetatable = setmetatable
+
+module 'gluon.site_config'
+
+setmetatable(_M,
+	{
+		__index = site_config,
+	}
+)
+
+return _M
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/sysconfig.lua b/package/gluon-core/files/usr/lib/lua/gluon/sysconfig.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ff61f05b53b48f59ddcf782e2c046f768172e875
--- /dev/null
+++ b/package/gluon-core/files/usr/lib/lua/gluon/sysconfig.lua
@@ -0,0 +1,34 @@
+local sysconfigdir = '/lib/gluon/core/sysconfig/'
+
+local function get(_, name)
+	local f = io.open(sysconfigdir .. name)
+	if f then
+		local ret = f:read('*line')
+		f:close()
+		return (ret or '')
+	end
+	return nil
+end
+
+local function set(_, name, val)
+	if val then
+		local f = io.open(sysconfigdir .. name, 'w+')
+		f:write(val)
+		f:close()
+	else
+		os.remove(sysconfigdir .. name)
+	end
+end
+
+local setmetatable = setmetatable
+
+module 'gluon.sysconfig'
+
+setmetatable(_M,
+	{
+		__index = get,
+		__newindex = set,
+	}
+)
+
+return _M
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/sysctl.lua b/package/gluon-core/files/usr/lib/lua/gluon/sysctl.lua
new file mode 100644
index 0000000000000000000000000000000000000000..44b0c21786133ef0b9a3649678899a96e0f2170a
--- /dev/null
+++ b/package/gluon-core/files/usr/lib/lua/gluon/sysctl.lua
@@ -0,0 +1,8 @@
+local util = require 'gluon.util'
+
+
+module 'gluon.sysctl'
+
+function set(name, value)
+	util.replace_prefix('/etc/sysctl.conf', name .. '=', name .. '=' .. value .. '\n')
+end
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/users.lua b/package/gluon-core/files/usr/lib/lua/gluon/users.lua
new file mode 100644
index 0000000000000000000000000000000000000000..8e618d88c74edf1862c30022b56ee3177d892b18
--- /dev/null
+++ b/package/gluon-core/files/usr/lib/lua/gluon/users.lua
@@ -0,0 +1,33 @@
+local util = require 'gluon.util'
+
+local os = os
+local string = string
+
+
+module 'gluon.users'
+
+function add_user(username, uid, gid)
+	util.lock('/var/lock/passwd')
+	util.replace_prefix('/etc/passwd', username .. ':', string.format('%s:*:%u:%u::/var:/bin/false\n', username, uid, gid))
+	util.replace_prefix('/etc/shadow', username .. ':', string.format('%s:*:0:0:99999:7:::\n', username))
+	util.unlock('/var/lock/passwd')
+end
+
+function remove_user(username)
+	util.lock('/var/lock/passwd')
+	util.replace_prefix('/etc/passwd', username .. ':')
+	util.replace_prefix('/etc/shadow', username .. ':')
+	util.unlock('/var/lock/passwd')
+end
+
+function add_group(groupname, gid)
+	util.lock('/var/lock/group')
+	util.replace_prefix('/etc/group', groupname .. ':', string.format('%s:x:%u:\n', groupname, gid))
+	util.unlock('/var/lock/group')
+end
+
+function remove_group(groupname)
+	util.lock('/var/lock/group')
+	util.replace_prefix('/etc/group', groupname .. ':')
+	util.unlock('/var/lock/group')
+end
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/util.lua b/package/gluon-core/files/usr/lib/lua/gluon/util.lua
new file mode 100644
index 0000000000000000000000000000000000000000..cf3677cb76611c97e0d87167512e00e1ceaff22e
--- /dev/null
+++ b/package/gluon-core/files/usr/lib/lua/gluon/util.lua
@@ -0,0 +1,79 @@
+-- Writes all lines from the file input to the file output except those starting with prefix
+-- Doesn't close the output file, but returns the file object
+local function do_filter_prefix(input, output, prefix)
+	local f = io.open(output, 'w+')
+	local l = prefix:len()
+
+	for line in io.lines(input) do
+		if line:sub(1, l) ~= prefix then
+			f:write(line, '\n')
+		end
+	end
+
+	return f
+end
+
+
+local function escape_args(ret, arg0, ...)
+	if not arg0 then
+		return ret
+	end
+
+	return escape_args(ret .. "'" .. string.gsub(arg0, "'", "'\\''") .. "' ", ...)
+end
+
+
+local os = os
+local string = string
+local tonumber = tonumber
+
+local nixio = require 'nixio'
+local sysconfig = require 'gluon.sysconfig'
+
+
+module 'gluon.util'
+
+function exec(...)
+	return os.execute(escape_args('', ...))
+end
+
+-- Removes all lines starting with a prefix from a file, optionally adding a new one
+function replace_prefix(file, prefix, add)
+	local tmp = file .. '.tmp'
+	local f = do_filter_prefix(file, tmp, prefix)
+	if add then
+		f:write(add)
+	end
+	f:close()
+	os.rename(tmp, file)
+end
+
+function lock(file)
+	exec('lock', file)
+end
+
+function unlock(file)
+	exec('lock', '-u', file)
+end
+
+function node_id()
+  return string.gsub(sysconfig.primary_mac, ':', '')
+end
+
+-- Generates a (hopefully) unique MAC address
+-- The first parameter defines the function and the second
+-- parameter an ID to add to the MAC address
+-- Functions and IDs defined so far:
+-- (1, 0): WAN (for mesh-on-WAN)
+-- (1, 1): LAN (for mesh-on-LAN)
+-- (2, n): client interface for the n'th radio
+-- (3, n): adhoc interface for n'th radio
+-- (4, 0): mesh VPN
+function generate_mac(f, i)
+  local m1, m2, m3, m4, m5, m6 = string.match(sysconfig.primary_mac, '(%x%x):(%x%x):(%x%x):(%x%x):(%x%x):(%x%x)')
+  m1 = nixio.bit.bor(tonumber(m1, 16), 0x02)
+  m2 = (tonumber(m2, 16)+f) % 0x100
+  m3 = (tonumber(m3, 16)+i) % 0x100
+
+  return string.format('%02x:%02x:%02x:%s:%s:%s', m1, m2, m3, m4, m5, m6)
+end
diff --git a/package/gluon-cron/Makefile b/package/gluon-cron/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..ac92a92dd7efe0e988b4214a78ac2d3380e2b549
--- /dev/null
+++ b/package/gluon-cron/Makefile
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-cron
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-cron
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Cron support
+  DEPENDS:=+gluon-core
+endef
+
+define Package/gluon-cron/description
+	Gluon community wifi mesh firmware framework: cron support
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+	$(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
+endef
+
+define Package/gluon-cron/install
+	$(CP) ./files/* $(1)/
+	$(INSTALL_DIR) $(1)/usr/sbin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-crond $(1)/usr/sbin/
+endef
+
+$(eval $(call BuildPackage,gluon-cron))
diff --git a/package/gluon-cron/files/etc/init.d/gluon-cron b/package/gluon-cron/files/etc/init.d/gluon-cron
new file mode 100755
index 0000000000000000000000000000000000000000..27a05e1d62125725433b622fc2505b7fa604cdc8
--- /dev/null
+++ b/package/gluon-cron/files/etc/init.d/gluon-cron
@@ -0,0 +1,18 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2013 Project Gluon
+
+START=50
+
+SERVICE_USE_PID=1
+SERVICE_WRITE_PID=1
+SERVICE_DAEMONIZE=1
+
+CRONDIR=/lib/gluon/cron
+
+start () {
+	service_start /usr/sbin/gluon-crond "$CRONDIR"
+}
+
+stop() {
+	service_stop /usr/sbin/gluon-crond
+}
diff --git a/package/gluon-cron/files/lib/gluon/cron/.keep b/package/gluon-cron/files/lib/gluon/cron/.keep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/package/gluon-cron/src/Makefile b/package/gluon-cron/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3f4c7a5012a0757c98f0a529feac6e02e7df8c87
--- /dev/null
+++ b/package/gluon-cron/src/Makefile
@@ -0,0 +1,3 @@
+all: gluon-crond
+
+gluon-crond: gluon-crond.c
diff --git a/package/gluon-cron/src/gluon-crond.c b/package/gluon-cron/src/gluon-crond.c
new file mode 100644
index 0000000000000000000000000000000000000000..11a87c42c04d329b440a33cfc399ed86045f0f97
--- /dev/null
+++ b/package/gluon-cron/src/gluon-crond.c
@@ -0,0 +1,316 @@
+/*
+  Copyright (c) 2013, Matthias Schiffer <mschiffer@universe-factory.net>
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+
+typedef struct job {
+	struct job *next;
+
+	uint64_t minutes;
+	uint32_t hours;
+	uint32_t doms;
+	uint16_t months;
+	uint8_t dows;
+
+	char *command;
+} job_t;
+
+
+static const char const *const MONTHS[12] = {
+	"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"
+};
+
+static const char const *const WEEKDAYS[7] = {
+	"sun", "mon", "tue", "wed", "thu", "fri", "sat"
+};
+
+
+static const char *crondir;
+
+static job_t *jobs = NULL;
+
+
+static void usage(void) {
+	fprintf(stderr, "Usage: gluon-crond <crondir>\n");
+}
+
+
+static inline uint64_t bit(unsigned b) {
+	return ((uint64_t)1) << b;
+}
+
+static int strict_atoi(const char *s) {
+	char *end;
+	int ret = strtol(s, &end, 10);
+
+	if (*end)
+		return -1;
+	else
+		return ret;
+}
+
+static uint64_t parse_strings(const char *input, const char *const *strings, size_t n) {
+	size_t i;
+	for (i = 0; i < n; i++) {
+		if (strcasecmp(input, strings[i]) == 0)
+			return bit(i);
+	}
+
+	return 0;
+}
+
+static uint64_t parse_times(char *input, int min, int n) {
+	uint64_t ret = 0;
+	int step = 1;
+
+	char *comma = strchr(input, ',');
+	if (comma) {
+		*comma = 0;
+		ret = parse_times(comma+1, min, n);
+
+		if (!ret)
+			return 0;
+	}
+
+	char *slash = strchr(input, '/');
+	if (slash) {
+		*slash = 0;
+		step = strict_atoi(slash+1);
+
+		if (step <= 0)
+			return 0;
+	}
+
+	int begin, end;
+	char *minus = strchr(input, '-');
+	if (minus) {
+		*minus = 0;
+		begin = strict_atoi(input);
+		end = strict_atoi(minus+1);
+	}
+	else if (strcmp(input, "*") == 0) {
+		begin = min;
+		end = min+n-1;
+	}
+	else {
+		begin = end = strict_atoi(input);
+	}
+
+	if (begin < min || end < min)
+		return 0;
+
+	int i;
+	for (i = begin-min; i <= end-min; i += step)
+		ret |= bit(i % n);
+
+	return ret;
+}
+
+static int handle_line(const char *line) {
+	job_t job = {};
+	int ret = -1;
+	char *columns[5];
+	int i;
+	int len;
+
+	int matches = sscanf(line, "%ms %ms %ms %ms %ms %n", &columns[0], &columns[1], &columns[2], &columns[3], &columns[4], &len);
+	if (matches != 5 && matches != 6) {
+		if (matches <= 0)
+			ret = 0;
+
+		goto end;
+	}
+
+	job.minutes = parse_times(columns[0], 0, 60);
+	if (!job.minutes)
+		goto end;
+
+	job.hours = parse_times(columns[1], 0, 24);
+	if (!job.hours)
+		goto end;
+
+	job.doms = parse_times(columns[2], 1, 31);
+	if (!job.doms)
+		goto end;
+
+
+	job.months = parse_strings(columns[3], MONTHS, 12);
+
+	if (!job.months)
+		job.months = parse_times(columns[3], 1, 12);
+	if (!job.months)
+		goto end;
+
+	job.dows = parse_strings(columns[4], WEEKDAYS, 7);
+	if (!job.dows)
+		job.dows = parse_times(columns[4], 0, 7);
+	if (!job.dows)
+		goto end;
+
+	job.command = strdup(line+len);
+
+	job_t *jobp = malloc(sizeof(job_t));
+	*jobp = job;
+
+	jobp->next = jobs;
+	jobs = jobp;
+
+	ret = 0;
+
+  end:
+	for (i = 0; i < matches && i < 5; i++)
+		free(columns[i]);
+
+	return ret;
+}
+
+
+static void read_crontab(const char *name) {
+	FILE *file = fopen(name, "r");
+	if (!file) {
+		syslog(LOG_WARNING, "unable to read crontab `%s'", name);
+		return;
+	}
+
+	char line[16384];
+	unsigned lineno = 0;
+
+	while (fgets(line, sizeof(line), file)) {
+		lineno++;
+
+		char *comment = strchr(line, '#');
+		if (comment)
+			*comment = 0;
+
+		if (handle_line(line))
+			syslog(LOG_WARNING, "syntax error in `%s', line %u", name, lineno);
+	}
+
+	fclose(file);
+}
+
+
+static void read_crondir(void) {
+	DIR *dir;
+
+	if (chdir(crondir) || ((dir = opendir(".")) == NULL)) {
+		fprintf(stderr, "Unable to read crondir `%s'\n", crondir);
+		usage();
+		exit(1);
+	}
+
+	struct dirent *ent;
+	while ((ent = readdir(dir)) != NULL) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		read_crontab(ent->d_name);
+	}
+
+	closedir(dir);
+}
+
+
+static void run_job(const job_t *job) {
+	pid_t pid = fork();
+	if (pid == 0) {
+		execl("/bin/sh", "/bin/sh", "-c", job->command, (char*)NULL);
+		syslog(LOG_ERR, "unable to run job: exec failed");
+		_exit(1);
+	}
+	else if (pid < 0) {
+		syslog(LOG_ERR, "unable to run job: fork failed");
+	}
+}
+
+
+static void check_job(const job_t *job, const struct tm *tm) {
+	if (!(job->minutes & bit(tm->tm_min)))
+		return;
+
+	if (!(job->hours & bit(tm->tm_hour)))
+		return;
+
+	if (!(job->doms & bit(tm->tm_mday-1)))
+		return;
+
+	if (!(job->months & bit(tm->tm_mon)))
+		return;
+
+	if (!(job->dows & bit(tm->tm_wday)))
+		return;
+
+	run_job(job);
+}
+
+
+int main(int argc, char *argv[]) {
+	if (argc != 2) {
+		usage();
+
+		exit(argc < 2 ? 0 : 1);
+	}
+
+	crondir = argv[1];
+
+	signal(SIGCHLD, SIG_IGN);
+
+	read_crondir();
+
+	time_t t = time(NULL);
+	struct tm *tm = localtime(&t);
+	int minute = tm->tm_min;
+
+	while (1) {
+		sleep(60 - t%60);
+
+		t = time(NULL);
+		tm = localtime(&t);
+
+		minute = (minute+1)%60;
+		if (tm->tm_min != minute) {
+			/* clock has moved, don't execute jobs */
+			minute = tm->tm_min;
+			continue;
+		}
+
+		job_t *job;
+		for (job = jobs; job; job = job->next)
+			check_job(job, tm);
+	}
+}
diff --git a/package/gluon-ebtables-filter-multicast/Makefile b/package/gluon-ebtables-filter-multicast/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..93b7f9a556bc7d3e4c7f9d064057d6815f7bb821
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/Makefile
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-ebtables-filter-multicast
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-ebtables-filter-multicast
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Ebtables filters for multicast packets
+  DEPENDS:=+gluon-core +gluon-ebtables
+endef
+
+define Package/gluon-ebtables-filter-multicast/description
+	Gluon community wifi mesh firmware framework: Ebtables filters for multicast packets
+
+	These filters drop non-essential multicast traffic before it enters the mesh.
+
+	Allowed protocols are: DHCP, DHCPv6, ARP, ICMP, ICMPv6, BitTorrent local peer discovery, BABEL and OSPF
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-ebtables-filter-multicast/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-ebtables-filter-multicast))
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/100-mcast-chain b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/100-mcast-chain
new file mode 100644
index 0000000000000000000000000000000000000000..ec0013a3b1a0e4afc7c6241c3b5966a7a9aa9a92
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/100-mcast-chain
@@ -0,0 +1 @@
+chain('MULTICAST_OUT', 'DROP')
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-arp b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-arp
new file mode 100644
index 0000000000000000000000000000000000000000..8af1900acb1d66d9f138c586cb8d23af05c1e5ff
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-arp
@@ -0,0 +1,3 @@
+rule 'MULTICAST_OUT -p ARP --arp-opcode Reply --arp-ip-src 0.0.0.0 -j DROP'
+rule 'MULTICAST_OUT -p ARP --arp-opcode Request --arp-ip-dst 0.0.0.0 -j DROP'
+rule 'MULTICAST_OUT -p ARP -j RETURN'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-babel b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-babel
new file mode 100644
index 0000000000000000000000000000000000000000..d5b81771acd9da6069710cfbd1b25923e3352a92
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-babel
@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol udp --ip6-destination-port 6696 -j RETURN'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-btlpd b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-btlpd
new file mode 100644
index 0000000000000000000000000000000000000000..20b709f81b164de4d24c15fcad9f1e55299792a6
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-btlpd
@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv4 --ip-destination 239.192.152.143 --ip-protocol udp --ip-destination-port 6771 -j RETURN'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv4 b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv4
new file mode 100644
index 0000000000000000000000000000000000000000..2fca222309524956787f9cfff7b2ce3fbaa3bac7
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv4
@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv4 --ip-protocol udp --ip-destination-port 67 -j RETURN'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv6 b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv6
new file mode 100644
index 0000000000000000000000000000000000000000..6d7f0f557e3002300bce746f4bf08d8ad61869e5
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv6
@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol udp --ip6-destination-port 547 -j RETURN'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmp b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmp
new file mode 100644
index 0000000000000000000000000000000000000000..25a95f39fc11b64d63286e112607b3b8b100984a
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmp
@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv4 --ip-protocol icmp -j RETURN'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmpv6 b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmpv6
new file mode 100644
index 0000000000000000000000000000000000000000..a7b6741491b6f7f199dbc60d53884f7be2e009db
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmpv6
@@ -0,0 +1,2 @@
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol 0 -j RETURN' -- hop-by-hop
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol ipv6-icmp -j RETURN'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-igmp b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-igmp
new file mode 100644
index 0000000000000000000000000000000000000000..2d3814ae8acc6a8ec68c05edd481760e69a0f6b6
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-igmp
@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv4 --ip-protocol igmp -j RETURN'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ospf b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ospf
new file mode 100644
index 0000000000000000000000000000000000000000..da928d4b362db62211e25107d2444a30826b971c
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ospf
@@ -0,0 +1,2 @@
+rule 'MULTICAST_OUT -p IPv4 --ip-protocol ospf -j RETURN'
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol ospf -j RETURN'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ripng b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ripng
new file mode 100644
index 0000000000000000000000000000000000000000..37d3187742094982b0b82608ee4bf29263fcad8c
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ripng
@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol udp --ip6-destination ff02::9 --ip6-destination-port 521 -j RETURN'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/300-mcast b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/300-mcast
new file mode 100644
index 0000000000000000000000000000000000000000..c52f122fccbc45e768be5cb365c67eb7592c3e81
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/300-mcast
@@ -0,0 +1,2 @@
+rule 'FORWARD --logical-out br-client -o bat0 -d Multicast -j MULTICAST_OUT'
+rule 'OUTPUT --logical-out br-client -o bat0 -d Multicast -j MULTICAST_OUT'
diff --git a/package/gluon-ebtables-filter-ra-dhcp/Makefile b/package/gluon-ebtables-filter-ra-dhcp/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..ea6a737d41dc35b50ad8eece7d7efb87155953bf
--- /dev/null
+++ b/package/gluon-ebtables-filter-ra-dhcp/Makefile
@@ -0,0 +1,39 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-ebtables-filter-ra-dhcp
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-ebtables-filter-ra-dhcp
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Ebtables filters for Router Advertisement and DHCP packets
+  DEPENDS:=+gluon-core +gluon-ebtables
+endef
+
+define Package/gluon-ebtables-filter-ra-dhcp/description
+	Gluon community wifi mesh firmware framework: Ebtables filters for Router Advertisement and DHCP packets
+
+	These filters ensure that RA and DHCP packets are only forwarded from the mesh into the
+	client network, and not vice-versa.
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-ebtables-filter-ra-dhcp/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-ebtables-filter-ra-dhcp))
diff --git a/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv4 b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv4
new file mode 100644
index 0000000000000000000000000000000000000000..ec56ff1d73e6d96a7ba6935f2ac9250769def123
--- /dev/null
+++ b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv4
@@ -0,0 +1,5 @@
+rule 'FORWARD -p IPv4 --ip-protocol udp --ip-destination-port 67 -j OUT_ONLY'
+rule 'OUTPUT -p IPv4 --ip-protocol udp --ip-destination-port 67 -j OUT_ONLY'
+
+rule 'FORWARD -p IPv4 --ip-protocol udp --ip-destination-port 68 -j IN_ONLY'
+rule 'INPUT -p IPv4 --ip-protocol udp --ip-destination-port 68 -j IN_ONLY'
diff --git a/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv6 b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv6
new file mode 100644
index 0000000000000000000000000000000000000000..470a76489c35585fc56b3e42ab81d96e5cccc5af
--- /dev/null
+++ b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv6
@@ -0,0 +1,5 @@
+rule 'FORWARD -p IPv6 --ip6-protocol udp --ip6-destination-port 547 -j OUT_ONLY'
+rule 'OUTPUT -p IPv6 --ip6-protocol udp --ip6-destination-port 547 -j OUT_ONLY'
+
+rule 'FORWARD -p IPv6 --ip6-protocol udp --ip6-destination-port 546 -j IN_ONLY'
+rule 'INPUT -p IPv6 --ip6-protocol udp --ip6-destination-port 546 -j IN_ONLY'
diff --git a/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-radv b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-radv
new file mode 100644
index 0000000000000000000000000000000000000000..b34d4c76de90d04990bb1f95f14b9ab7dd5fba4d
--- /dev/null
+++ b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-radv
@@ -0,0 +1,5 @@
+rule 'FORWARD -p IPv6 --ip6-protocol ipv6-icmp --ip6-icmp-type router-solicitation -j OUT_ONLY'
+rule 'OUTPUT -p IPv6 --ip6-protocol ipv6-icmp --ip6-icmp-type router-solicitation -j OUT_ONLY'
+
+rule 'FORWARD -p IPv6 --ip6-protocol ipv6-icmp --ip6-icmp-type router-advertisement -j IN_ONLY'
+rule 'INPUT -p IPv6 --ip6-protocol ipv6-icmp --ip6-icmp-type router-advertisement -j IN_ONLY'
diff --git a/package/gluon-ebtables/Makefile b/package/gluon-ebtables/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..39c654c1bfd845e81da609d1d20ebeb30e745a90
--- /dev/null
+++ b/package/gluon-ebtables/Makefile
@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-ebtables
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-ebtables
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Ebtables support
+  DEPENDS:=+gluon-core +ebtables +kmod-ebtables-ipv4 +kmod-ebtables-ipv6 +kmod-ipt-core
+endef
+
+define Package/gluon-ebtables/description
+	Gluon community wifi mesh firmware framework: ebtables support
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-ebtables/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-ebtables))
diff --git a/package/gluon-ebtables/files/etc/init.d/gluon-ebtables b/package/gluon-ebtables/files/etc/init.d/gluon-ebtables
new file mode 100755
index 0000000000000000000000000000000000000000..5a770452822e1a690172746bc4a94111e197ccb3
--- /dev/null
+++ b/package/gluon-ebtables/files/etc/init.d/gluon-ebtables
@@ -0,0 +1,73 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2013 Project Gluon
+#
+# Firewall script for inserting and removing ebtables rules.
+#
+# Example format, for filtering any IPv4 multicast packets to the SSDP UDP port:
+# rule FORWARD --logical-out br-client -d Multicast -p IPv4 --ip-protocol udp --ip-destination-port 5355 -j DROP
+#
+# Removing all rules:
+# $ ./firewall-ebtables stop
+# Inserting all rules:
+# $ ./firewall-ebtables start
+# Inserting a specific rule file:
+# $ ./firewall-ebtables start /lib/gluon/ebtables/100-mcast-chain
+# Removing a specific rule file:
+# $ ./firewall-ebtables stop /lib/gluon/ebtables/100-mcast-chain
+
+
+START=19
+STOP=91
+
+
+exec_file() {
+	local file="$1"
+
+	/usr/bin/lua -e "
+		function rule(command)
+			os.execute($EBTABLES_RULE)
+		end
+		function chain(name, policy)
+			os.execute($EBTABLES_CHAIN)
+		end
+	" "$file"
+}
+
+exec_all() {
+	local sort_arg="$1"
+
+	local old_ifs="$IFS"
+	IFS='
+'
+	for file in `find /lib/gluon/ebtables -type f | sort $sort_arg`; do
+		exec_file "$file"
+	done
+	IFS="$old_ifs"
+}
+
+
+start() {
+	(
+		export EBTABLES_RULE='"ebtables -A " .. command'
+		export EBTABLES_CHAIN='"ebtables -N " .. name .. " -P " .. policy'
+
+		if [ -z "$1" ]; then
+			exec_all ''
+		else
+			exec_file "$1"
+		fi
+	)
+}
+
+stop() {
+	(
+		export EBTABLES_RULE='"ebtables -D " .. command'
+		export EBTABLES_CHAIN='"ebtables -X " .. name'
+
+		if [ -z "$1" ]; then
+			exec_all '-r'
+		else
+			exec_file "$1"
+		fi
+	)
+}
diff --git a/package/gluon-ebtables/files/lib/gluon/ebtables/100-dir-chain b/package/gluon-ebtables/files/lib/gluon/ebtables/100-dir-chain
new file mode 100644
index 0000000000000000000000000000000000000000..31c19c534fc87fe977eebd2b1497dd1d779db29e
--- /dev/null
+++ b/package/gluon-ebtables/files/lib/gluon/ebtables/100-dir-chain
@@ -0,0 +1,2 @@
+chain('IN_ONLY', 'RETURN')
+chain('OUT_ONLY', 'RETURN')
diff --git a/package/gluon-ebtables/files/lib/gluon/ebtables/101-dir-rules b/package/gluon-ebtables/files/lib/gluon/ebtables/101-dir-rules
new file mode 100644
index 0000000000000000000000000000000000000000..b1cd4e2468be728b23195d423b805a97b651c4fe
--- /dev/null
+++ b/package/gluon-ebtables/files/lib/gluon/ebtables/101-dir-rules
@@ -0,0 +1,2 @@
+rule 'IN_ONLY --logical-in br-client -i ! bat0 -j DROP'
+rule 'OUT_ONLY --logical-out br-client -o ! bat0 -j DROP'
diff --git a/package/gluon-legacy/Makefile b/package/gluon-legacy/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..7320fba6384e6eaad1be7972154a908d6b098d9e
--- /dev/null
+++ b/package/gluon-legacy/Makefile
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-legacy
+PKG_VERSION:=2
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-legacy
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Legacy update scripts
+  DEPENDS:=+gluon-core
+endef
+
+define Package/gluon-legacy/description
+	Gluon community wifi mesh firmware framework: legacy update scripts
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-legacy/install
+	$(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-legacy/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-legacy))
diff --git a/package/gluon-legacy/check_site.lua b/package/gluon-legacy/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..1ec26de07a904396202721a74755aab8c5410e53
--- /dev/null
+++ b/package/gluon-legacy/check_site.lua
@@ -0,0 +1,8 @@
+need_string_array 'legacy.version_files'
+need_string_array 'legacy.old_files'
+
+need_string_array 'legacy.config_mode_configs'
+need_string_array 'legacy.fastd_configs'
+need_string 'legacy.mesh_ifname'
+need_string_array 'legacy.tc_configs'
+need_string_array 'legacy.wifi_names'
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/000-legacy b/package/gluon-legacy/files/lib/gluon/upgrade/000-legacy
new file mode 100755
index 0000000000000000000000000000000000000000..78436676de078f121215257d44a40f16895bbc0a
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/000-legacy
@@ -0,0 +1,11 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+for _, file in ipairs(site.legacy.version_files) do
+  if os.remove(file) then
+    -- Set version being upgraded from to 'legacy'
+    sysconfig.gluon_version = 'legacy'
+  end
+end
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/019-legacy-interfaces b/package/gluon-legacy/files/lib/gluon/upgrade/019-legacy-interfaces
new file mode 100755
index 0000000000000000000000000000000000000000..b48e42b875f5b7683129133a7e394ed69a0caba3
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/019-legacy-interfaces
@@ -0,0 +1,40 @@
+#!/usr/bin/lua
+
+local gluon_util = require 'gluon.util'
+local platform = require 'gluon.platform'
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+local uci = require('luci.model.uci').cursor()
+local util = require 'luci.util'
+
+
+if sysconfig.gluon_version == 'legacy' then
+  local function iface_exists(name)
+    return (gluon_util.exec('ip', 'link', 'show', 'dev', (name:gsub('%..*$', ''))) == 0)
+  end
+
+  local function remove_bat0(iface)
+    return util.trim(string.gsub(' ' .. iface .. ' ', ' bat0 ', ' '))
+  end
+
+
+  local lan_ifname = remove_bat0(uci:get('network', site.legacy.mesh_ifname, 'ifname'))
+  local wan_ifname = uci:get('network', 'wan', 'ifname')
+
+  if wan_ifname and iface_exists(wan_ifname) then
+    sysconfig.wan_ifname = wan_ifname
+    sysconfig.lan_ifname = lan_ifname
+  else
+    sysconfig.wan_ifname = lan_ifname
+  end
+
+
+  uci:delete('network', site.legacy.mesh_ifname)
+  uci:delete('network', 'wan')
+
+  uci:save('network')
+  uci:commit('network')
+end
+
+
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/210-legacy-wireless b/package/gluon-legacy/files/lib/gluon/upgrade/210-legacy-wireless
new file mode 100755
index 0000000000000000000000000000000000000000..36da0632adc3b6d104de56eee188ecf90e4a24c4
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/210-legacy-wireless
@@ -0,0 +1,24 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+local uci = require('luci.model.uci').cursor()
+
+
+if sysconfig.gluon_version == 'legacy' then
+  function delete_legacy_iface(iface)
+    for _, wifi in pairs(site.legacy.wifi_names) do
+      if wifi == iface['.name'] then
+	return true
+      end
+    end
+
+    return false
+  end
+
+  uci:delete_all('wireless', 'wifi-iface', delete_legacy_iface)
+
+  uci:save('wireless')
+  uci:commit('wireless')
+end
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-setup-mode b/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-setup-mode
new file mode 100755
index 0000000000000000000000000000000000000000..0b97e1209f35f4fd6a4220e5509ac3c95cafa0c7
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-setup-mode
@@ -0,0 +1,22 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+local uci = require('luci.model.uci').cursor()
+
+
+if sysconfig.gluon_version == 'legacy' then
+  for _, config in ipairs(site.legacy.config_mode_configs) do
+    local old = uci:get_first(config, 'wizard', 'configured')
+    if old == '1' then
+      local setup_mode = uci:get_first('gluon-setup-mode', 'setup_mode')
+      uci:set('gluon-setup-mode', setup_mode, 'configured', '1')
+
+      uci:save('gluon-setup-mode')
+      uci:commit('gluon-setup-mode')
+
+      break
+    end
+  end
+end
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-simple-tc b/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-simple-tc
new file mode 100755
index 0000000000000000000000000000000000000000..c67afe14b5eba4d186a87d1654cbafdd126e45ba
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-simple-tc
@@ -0,0 +1,29 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+local uci = require('luci.model.uci').cursor()
+
+
+if sysconfig.gluon_version == 'legacy' then
+  for _, config in ipairs(site.legacy.tc_configs) do
+    local s = uci:get_first(config, 'bandwidth')
+    if s then
+      old = uci:get_all(config, s)
+      uci:section('gluon-simple-tc', 'interface', 'mesh_vpn',
+		{
+		  ifname = 'mesh-vpn',
+		  enabled = old.enabled,
+		  limit_ingress = old.downstream,
+		  limit_egress = old.upstream,
+		}
+      )
+
+      uci:save('gluon-simple-tc')
+      uci:commit('gluon-simple-tc')
+
+      break
+    end
+  end
+end
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/390-legacy-mesh-vpn-fastd b/package/gluon-legacy/files/lib/gluon/upgrade/390-legacy-mesh-vpn-fastd
new file mode 100755
index 0000000000000000000000000000000000000000..468a35a6501eaed257e15164f31be1b84d98d689
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/390-legacy-mesh-vpn-fastd
@@ -0,0 +1,37 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+local uci = require('luci.model.uci').cursor()
+
+
+if sysconfig.gluon_version == 'legacy' then
+  local secret
+  local enabled
+
+
+  for _, config in ipairs(site.legacy.fastd_configs) do
+    if not secret then
+      local s = uci:get_all('fastd', config)
+      if s then
+	secret = s.secret
+	enabled = s.enabled
+      end
+    end
+
+    uci:delete('fastd', config)
+  end
+
+  if secret then
+    uci:section('fastd', 'fastd', 'mesh_vpn',
+		{
+		  secret = secret,
+		  enabled = enabled,
+		}
+    )
+  end
+
+  uci:save('fastd')
+  uci:commit('fastd')
+end
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/990-legacy-late b/package/gluon-legacy/files/lib/gluon/upgrade/990-legacy-late
new file mode 100755
index 0000000000000000000000000000000000000000..efb6b675ea0e144c4c3132b5375fc6cd921804bf
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/990-legacy-late
@@ -0,0 +1,11 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+
+if sysconfig.gluon_version == 'legacy' then
+  for _, file in ipairs(site.legacy.old_files) do
+    os.remove(file)
+  end
+end
diff --git a/package/gluon-lock-password/Makefile b/package/gluon-lock-password/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d0e99373222afeab5900d84ebc7e20ab8e0aefcf
--- /dev/null
+++ b/package/gluon-lock-password/Makefile
@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-lock-password
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-lock-password
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Locks the root account by default
+  DEPENDS:=+gluon-core
+endef
+
+define Package/gluon-lock-password/description
+	This packages locks the root account by default.
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-lock-password/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-lock-password))
diff --git a/package/gluon-lock-password/files/lib/gluon/upgrade/100-lock-password b/package/gluon-lock-password/files/lib/gluon/upgrade/100-lock-password
new file mode 100755
index 0000000000000000000000000000000000000000..3204f638b7a5fa12a84d26d1ba84d3185647c4e1
--- /dev/null
+++ b/package/gluon-lock-password/files/lib/gluon/upgrade/100-lock-password
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+has_root_pwd() {
+	local pwd
+
+	pwd=$([ -f "$1" ] && cat "$1")
+	pwd="${pwd#*root:}"
+	pwd="${pwd%%:*}"
+
+	test -n "${pwd}"
+}
+
+has_root_pwd /etc/shadow || passwd -l root
diff --git a/package/gluon-luci-admin/Makefile b/package/gluon-luci-admin/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..455bcb25b973dd2323125c2aae0a74a77aba91cf
--- /dev/null
+++ b/package/gluon-luci-admin/Makefile
@@ -0,0 +1,39 @@
+# Copyright (C) 2013 Nils Schneider <nils at nilsschneider.net>
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-admin
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-luci-admin
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Luci based simple administration interface for mesh nodes
+  DEPENDS:=+gluon-config-mode-core
+endef
+
+define Package/gluon-luci-admin/description
+	Luci based config mode
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-admin/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-luci-admin))
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/index.lua b/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/index.lua
new file mode 100644
index 0000000000000000000000000000000000000000..284cc162be5023198d0941cf708585bde8ea3fd4
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/index.lua
@@ -0,0 +1,39 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]--
+
+module("luci.controller.admin.index", package.seeall)
+
+function index()
+	local uci_state = luci.model.uci.cursor_state()
+
+	-- Disable gluon-luci-admin when setup mode is not enabled
+	if uci_state:get_first('gluon-setup-mode', 'setup_mode', 'running', '0') ~= '1' then
+		return
+	end
+
+	local root = node()
+	if not root.lock then
+		root.target = alias("admin")
+		root.index = true
+	end
+
+	local page = entry({"admin"}, alias("admin", "index"), "Expert Mode", 10)
+	page.sysauth = "root"
+	page.sysauth_authenticator = function() return "root" end
+	page.index = true
+
+	entry({"admin", "index"}, cbi("admin/info"), _("Info"), 1).ignoreindex = true
+	entry({"admin", "remote"}, cbi("admin/remote"), _("Remotezugriff"), 10)
+end
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/upgrade.lua b/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/upgrade.lua
new file mode 100644
index 0000000000000000000000000000000000000000..868c2bbdb2f8ea8a30a62d41477fae36c4b76962
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/upgrade.lua
@@ -0,0 +1,135 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]--
+
+module("luci.controller.admin.upgrade", package.seeall)
+
+function index()
+	local has_platform = nixio.fs.access("/lib/upgrade/platform.sh")
+	if has_platform then
+		entry({"admin", "upgrade"}, call("action_upgrade"), "Firmware aktualisieren", 90)
+		entry({"admin", "upgrade", "reboot"}, template("admin/upgrade_reboot"), nil, nil)
+	end
+end
+
+function action_upgrade()
+	local tmpfile = "/tmp/firmware.img"
+
+	-- Install upload handler
+	local file
+	luci.http.setfilehandler(
+		function(meta, chunk, eof)
+			if not nixio.fs.access(tmpfile) and not file and chunk and #chunk > 0 then
+				file = io.open(tmpfile, "w")
+			end
+			if file and chunk then
+				file:write(chunk)
+			end
+			if file and eof then
+				file:close()
+			end
+		end
+	)
+
+	-- Determine state
+	local step         = tonumber(luci.http.formvalue("step") or 1)
+	local has_image    = nixio.fs.access(tmpfile)
+	local has_support  = image_supported(tmpfile)
+
+	-- Step 1: file upload, error on unsupported image format
+	if not has_image or not has_support or step == 1 then
+		-- If there is an image but user has requested step 1
+		-- or type is not supported, then remove it.
+		if has_image then
+			nixio.fs.unlink(tmpfile)
+		end
+
+		luci.template.render("admin/upgrade", {
+			bad_image=(has_image and not has_support or false)
+		} )
+
+	-- Step 2: present uploaded file, show checksum, confirmation
+	elseif step == 2 then
+		luci.template.render("admin/upgrade_confirm", {
+			checksum=image_checksum(tmpfile),
+			filesize=nixio.fs.stat(tmpfile).size,
+			flashsize=storage_size(),
+			keepconfig=luci.http.formvalue("keepcfg") == "1"
+		} )
+	elseif step == 3 then
+		local keepcfg = luci.http.formvalue("keepcfg") == "1"
+		fork_exec("/sbin/sysupgrade %s %q" % { keepcfg and "" or "-n", tmpfile })
+		luci.http.redirect(luci.dispatcher.build_url("admin", "upgrade", "reboot"))
+	end
+end
+
+function fork_exec(command)
+	local pid = nixio.fork()
+	if pid > 0 then
+		return
+	elseif pid == 0 then
+		-- change to root dir
+		nixio.chdir("/")
+
+		-- patch stdin, out, err to /dev/null
+		local null = nixio.open("/dev/null", "w+")
+		if null then
+			nixio.dup(null, nixio.stderr)
+			nixio.dup(null, nixio.stdout)
+			nixio.dup(null, nixio.stdin)
+			if null:fileno() > 2 then
+				null:close()
+			end
+		end
+
+		-- replace with target command
+		nixio.exec("/bin/sh", "-c", command)
+	end
+end
+
+function image_supported(tmpfile)
+	-- XXX: yay...
+	return ( 0 == os.execute(
+		". /lib/functions.sh; " ..
+		"include /lib/upgrade; " ..
+		"platform_check_image %q >/dev/null"
+			% tmpfile
+	) )
+end
+
+function storage_size()
+	local size = 0
+	if nixio.fs.access("/proc/mtd") then
+		for l in io.lines("/proc/mtd") do
+			local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
+			if n == "linux" then
+				size = tonumber(s, 16)
+				break
+			end
+		end
+	elseif nixio.fs.access("/proc/partitions") then
+		for l in io.lines("/proc/partitions") do
+			local x, y, b, n = l:match('^%s*(%d+)%s+(%d+)%s+([^%s]+)%s+([^%s]+)')
+			if b and n and not n:match('[0-9]') then
+				size = tonumber(b) * 1024
+				break
+			end
+		end
+	end
+	return size
+end
+
+function image_checksum(tmpfile)
+	return (luci.sys.exec("md5sum %q" % tmpfile):match("^([^%s]+)"))
+end
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/info.lua b/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/info.lua
new file mode 100644
index 0000000000000000000000000000000000000000..e9ceba70bbfd1a059cdca17623cd3336626ef161
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/info.lua
@@ -0,0 +1,4 @@
+local t = Template('admin/info')
+t.pageaction = false
+
+return t
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/remote.lua b/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/remote.lua
new file mode 100644
index 0000000000000000000000000000000000000000..dfba5e66b7136264287619c9aab3fd733170371b
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/remote.lua
@@ -0,0 +1,108 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2011 Jo-Philipp Wich <xm@subsignal.org>
+Copyright 2013 Nils Schneider <nils@nilsschneider.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]--
+
+local fs = require "nixio.fs"
+
+local m = Map("system", "SSH-Keys")
+m.submit = "Speichern"
+m.reset = "Zurücksetzen"
+m.pageaction = false
+m.template = "admin/expertmode"
+
+if fs.access("/etc/config/dropbear") then
+  local s = m:section(TypedSection, "_dummy1", nil,
+    "Hier hast du die Möglichkeit SSH-Keys (einen pro Zeile) zu hinterlegen:")
+
+  s.addremove = false
+  s.anonymous = true
+
+  function s.cfgsections()
+    return { "_keys" }
+  end
+
+  local keys
+
+  keys = s:option(TextValue, "_data", "")
+  keys.wrap    = "off"
+  keys.rows    = 5
+  keys.rmempty = true
+
+  function keys.cfgvalue()
+    return fs.readfile("/etc/dropbear/authorized_keys") or ""
+  end
+
+  function keys.write(self, section, value)
+    if value then
+      fs.writefile("/etc/dropbear/authorized_keys", value:gsub("\r\n", "\n"))
+    end
+  end
+
+  function keys.remove(self, section)
+    if keys:formvalue("_keys") then
+      fs.remove("/etc/dropbear/authorized_keys")
+    end
+  end
+end
+
+local m2 = Map("system", "Passwort")
+m2.submit = "Speichern"
+m2.reset = false
+m2.pageaction = false
+m2.template = "admin/expertmode"
+
+local s = m2:section(TypedSection, "_dummy2", nil,
+[[Alternativ kannst du auch ein Passwort setzen. Wähle bitte ein sicheres Passwort, das du nirgendwo anders verwendest.<br /><br />
+Beim Setzen eines leeren Passworts wird der Login per Passwort gesperrt (dies ist die Standard-Einstellung).]])
+
+s.addremove = false
+s.anonymous = true
+
+local pw1 = s:option(Value, "pw1", "Passwort")
+pw1.password = true
+
+local pw2 = s:option(Value, "pw2", "Wiederholung")
+pw2.password = true
+
+function s.cfgsections()
+  return { "_pass" }
+end
+
+function m2.on_commit(map)
+  local v1 = pw1:formvalue("_pass")
+  local v2 = pw2:formvalue("_pass")
+
+  if v1 and v2 then
+    if v1 == v2 then
+      if #v1 > 0 then
+	if luci.sys.user.setpasswd(luci.dispatcher.context.authuser, v1) == 0 then
+          m2.message = "Passwort geändert."
+	else
+          m2.errmessage = "Das Passwort konnte nicht geändert werden."
+	end
+      else
+        -- We don't check the return code here as the error 'password for root is already locked' is normal...
+        os.execute('passwd -l root >/dev/null')
+        m2.message = "Passwort gelöscht."
+      end
+    else
+      m2.errmessage = "Die beiden Passwörter stimmen nicht überein."
+    end
+  end
+end
+
+local c = Compound(m, m2)
+c.pageaction = false
+return c
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/expertmode.htm b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/expertmode.htm
new file mode 100644
index 0000000000000000000000000000000000000000..53947f3c3bab2a2988dbf4c11b0eef0c665b6777
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/expertmode.htm
@@ -0,0 +1,56 @@
+<% if not self.embedded then %>
+<form method="post" enctype="multipart/form-data" action="<%=REQUEST_URI%>">
+	<div>
+		<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+		<input type="hidden" name="cbi.submit" value="1" />
+	</div>
+<% end %>
+	<div class="cbi-map" id="cbi-<%=self.config%>">
+		<% if self.title and #self.title > 0 then %><h2><a id="content" name="content"><%=self.title%></a></h2><% end %>
+		<%- if self.message then %>
+			<p class="message success"><%=self.message%></p>
+		<%- end %>
+		<%- if self.errmessage then %>
+			<p class="message error"><%=self.errmessage%></p>
+		<%- end %>
+		<% if self.description and #self.description > 0 then %><div class="cbi-map-descr"><%=self.description%></div><% end %>
+		<% self:render_children() %>
+	</div>
+<% if not self.embedded then %>
+	<div class="cbi-page-actions">
+<%-
+	if type(self.hidden) == "table" then
+		for k, v in pairs(self.hidden) do
+-%>
+	<input type="hidden" id="<%=k%>" name="<%=k%>" value="<%=pcdata(v)%>" />
+<%-
+		end
+	end
+%>
+<% if redirect then %>
+	<div style="float:left">
+		<input class="cbi-button cbi-button-link" type="button" value="<%:Back to Overview%>" onclick="location.href='<%=pcdata(redirect)%>'" />
+	</div>
+<% end %>
+<%- if self.flow and self.flow.skip then %>
+	<input class="cbi-button cbi-button-skip" type="submit" name="cbi.skip" value="<%:Skip%>" />
+<% end %>
+<%- if self.submit ~= false then %>
+	<input class="cbi-button cbi-button-save" type="submit" name="cbi.apply" value="
+		<%- if not self.submit then -%><%-:Submit-%><%-else-%><%=self.submit%><%end-%>
+	" />
+<% end %>
+<%- if self.reset ~= false then %>
+	<input class="cbi-button cbi-button-reset" type="reset" value="
+		<%- if not self.reset then -%><%-:Reset-%><%-else-%><%=self.reset%><%end-%>
+	" />
+<% end %>
+<%- if self.cancel ~= false and self.on_cancel then %>
+	<input class="cbi-button cbi-button-reset" type="submit" name="cbi.cancel" value="
+		<%- if not self.cancel then -%><%-:Cancel-%><%-else-%><%=self.cancel%><%end-%>
+	" />
+<% end %>
+		<script type="text/javascript">cbi_d_update();</script>
+	</div>
+</form>
+<% end %>
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/info.htm b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/info.htm
new file mode 100644
index 0000000000000000000000000000000000000000..9c3843995211636273c09701ba5e1e4bc2512aa6
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/info.htm
@@ -0,0 +1,44 @@
+<%-
+   local fs = require 'luci.fs'
+   local uci = require('luci.model.uci').cursor()
+   local util = require 'luci.util'
+
+   local site = require 'gluon.site_config'
+   local sysconfig = require 'gluon.sysconfig'
+   local platform = require 'gluon.platform'
+
+
+   local keys = {
+     hostname = 'Hostname',
+     primary_mac = 'MAC-Adresse',
+     model = 'Hardware-Modell',
+     version = 'Gluon-Version',
+     release = 'Firmware-Release',
+     site = 'Site',
+     pubkey = 'Öffentlicher VPN-Schlüssel',
+   }
+
+   local values = {
+     hostname = uci:get_first('system', 'system', 'hostname'),
+     primary_mac = sysconfig.primary_mac,
+     model = platform.get_model(),
+     version = util.trim(fs.readfile('/lib/gluon/gluon-version')),
+     release = util.trim(fs.readfile('/lib/gluon/release')),
+     site = site.site_name,
+     pubkey = 'n/a',
+   }
+
+   local meshvpn_enabled = uci:get("fastd", "mesh_vpn", "enabled", "0")
+   if meshvpn_enabled == "1" then
+     local pubkey = util.trim(util.exec('/etc/init.d/fastd show_key mesh_vpn'))
+     if pubkey ~= '' then
+       values.pubkey = pubkey
+     end
+   end
+-%>
+<h2>Info</h2>
+<% for _, key in ipairs({'hostname', 'primary_mac', 'model', 'version', 'release', 'site', 'pubkey'}) do %>
+<div class="cbi-value">
+  <div class="cbi-value-title"><%=keys[key]%></div><div class="cbi-value-field"><%=values[key] or 'n/a'%></div>
+</div>
+<% end %>
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade.htm b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade.htm
new file mode 100644
index 0000000000000000000000000000000000000000..343ed4952dd4ea89cd9fa500a862faeb2709dd78
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade.htm
@@ -0,0 +1,55 @@
+<%#
+LuCI - Lua Configuration Interface
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008-2009 Jo-Philipp Wich <xm@subsignal.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+
+-%>
+
+<%+header%>
+
+<h2>Firmware aktualisieren</h2>
+
+<form method="post" action="<%=REQUEST_URI%>" enctype="multipart/form-data">
+<p>
+  Hier kannst du ein manuelles Firmwareupdate durchführen.
+</p>
+<% if bad_image then %>
+<p class="error">Die übermittelte Firmwaredatei kann nicht verwendet werden.</p>
+<% end %>
+<div class="cbi-section-node">
+  <div class="cbi-value">
+    <label class="cbi-value-title">
+      Firmware-Datei
+    </label>
+    <div class="cbi-value-field">
+      <input class="cbi-input-file" type="file" name="image" />
+    </div>
+  </div>
+
+  <div class="cbi-value cbi-value-last">
+    <label class="cbi-value-title">
+      Einstellungen beibehalten
+    </label>
+
+    <div class="cbi-value-field">
+      <input id="keepcfg" class="cbi-input-checkbox" type="checkbox" name="keepcfg" value="1" checked="checked" />
+      <label for="keepcfg"></label>
+    </div>
+  </div>
+</div>
+
+<div class="cbi-page-actions right">
+  <input type="hidden" name="step" value="2" />
+  <input class="cbi-button cbi-button-apply" type="submit" value="Upload image" />
+</div>
+</form>
+<%+footer%>
+
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade_confirm.htm b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade_confirm.htm
new file mode 100644
index 0000000000000000000000000000000000000000..ac3757619f32c2c106c80b8d29d3259ad6302dc7
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade_confirm.htm
@@ -0,0 +1,67 @@
+<%#
+LuCI - Lua Configuration Interface
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008-2009 Jo-Philipp Wich <xm@subsignal.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+
+-%>
+
+<%+header%>
+
+<h2>Firmware aktualisieren</h2>
+<p>
+    Die Firmwaredatei wurde übermittelt. Bitte vergleiche MD5-Checksumme
+    und Dateigröße und klicke anschließend auf "Fortfahren".
+</p>
+
+<% if flashsize > 0 and filesize > flashsize then %>
+<p class="error">Die Firmware passt nicht in den Speicher des Gerätes.</p>
+<% end %>
+
+<p>
+  <ul>
+    <li>md5sum: <code><%=checksum%></code></li>
+    <li>Größe: <%
+      function byte_format(byte)
+        local suff = {"B", "KB", "MB", "GB", "TB"}
+        for i=1, 5 do
+          if byte > 1024 and i < 5 then
+            byte = byte / 1024
+          else
+            return string.format("%.2f %s", byte, suff[i])
+          end
+        end
+      end
+
+      write(byte_format(filesize))
+
+      if flashsize > 0 then
+        write(luci.i18n.translatef(
+          " (%s available)",
+          w.byte_format(flashsize)
+        ))
+      end
+    %></li>
+  </ul>
+</p>
+<div class="cbi-page-actions right">
+  <form style="display:inline">
+    <input type="hidden" name="step" value="3" />
+    <input type="hidden" name="keepcfg" value="<%=keepconfig and "1" or "0"%>" />
+    <input class="cbi-button cbi-button-apply" type="submit" value="Fortfahren" />	
+  </form>
+  <form style="display:inline">
+    <input type="hidden" name="step" value="1" />
+    <input type="hidden" name="keepcfg" value="<%=keepconfig and "1" or "0"%>" />
+    <input class="cbi-button cbi-button-reset" type="submit" value="Abbrechen" />
+  </form>
+</div>
+<%+footer%>
+
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade_reboot.htm b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade_reboot.htm
new file mode 100644
index 0000000000000000000000000000000000000000..b9baa2794fb21edfbb9f48230e5669a7cb0c2601
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade_reboot.htm
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%=luci.i18n.context.lang%>" lang="<%=luci.i18n.context.lang%>">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <title>Firmware wird aktualisiert</title>
+    <link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />
+  </head>
+  <body>
+    <div id="maincontainer">
+      <div id="maincontent">
+        <p>
+          Die Firmware wird jetzt aktualisiert.
+          <strong>UNTERBRICH AUF KEINEN FALL DIE STROMVERSORGUNG!</strong>
+          Dieser Vorgang wird einige Minuten dauern.
+          Anschließend startet das Gerät automatisch neu.
+        </p>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/package/gluon-luci-autoupdater/Makefile b/package/gluon-luci-autoupdater/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..518bd6df2447a5a39461c9e38c45b55a29e7df78
--- /dev/null
+++ b/package/gluon-luci-autoupdater/Makefile
@@ -0,0 +1,39 @@
+# Copyright (C) 2013 Nils Schneider <nils at nilsschneider.net>
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-autoupdater
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-luci-autoupdater
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Luci module for gluon-autoupdater
+  DEPENDS:=+gluon-luci-admin +gluon-autoupdater
+endef
+
+define Package/gluon-luci-autoupdater/description
+	Luci module for gluon-autoupdater
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-autoupdater/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-luci-autoupdater))
diff --git a/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/controller/admin/autoupdater.lua b/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/controller/admin/autoupdater.lua
new file mode 100644
index 0000000000000000000000000000000000000000..9e6fd79f1a8c84b848f7a257ae7d21633d50f39b
--- /dev/null
+++ b/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/controller/admin/autoupdater.lua
@@ -0,0 +1,20 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2013 Nils Schneider <nils@nilsschneider.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]--
+
+module("luci.controller.admin.autoupdater", package.seeall)
+
+function index()
+        entry({"admin", "autoupdater"}, cbi("admin/autoupdater"), _("Autoupdater"), 20)
+end
+
diff --git a/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/model/cbi/admin/autoupdater.lua b/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/model/cbi/admin/autoupdater.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6800422b989d4e29eda1f97b3cd2e13c2acef5df
--- /dev/null
+++ b/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/model/cbi/admin/autoupdater.lua
@@ -0,0 +1,31 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2013 Nils Schneider <nils@nilsschneider.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]--
+
+m = Map("autoupdater", "Autoupdater")
+m.submit = "Speichern"
+m.reset = "Zurücksetzen"
+m.pageaction = false
+m.template = "admin/expertmode"
+
+s = m:section(TypedSection, "autoupdater", nil)
+s.addremove = false
+s.anonymous = true
+
+s:option(Flag, "enabled", "Aktivieren")
+f = s:option(ListValue, "branch", "Branch")
+
+uci.cursor():foreach("autoupdater", "branch", function (section) f:value(section[".name"]) end)
+
+return m
+
diff --git a/package/gluon-luci-node-role/Makefile b/package/gluon-luci-node-role/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..5e0fbb00c2cd784e2dac4fee90bd0f083fda7021
--- /dev/null
+++ b/package/gluon-luci-node-role/Makefile
@@ -0,0 +1,37 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-node-role
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-luci-node-role
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  DEPENDS:=+gluon-luci-admin +gluon-node-info
+  TITLE:=UI for specifying node role
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-node-role/install
+	$(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-luci-node-role/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-luci-node-role))
diff --git a/package/gluon-luci-node-role/check_site.lua b/package/gluon-luci-node-role/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..cd3ca9378a7c30e6806460e7e714601502d3ed87
--- /dev/null
+++ b/package/gluon-luci-node-role/check_site.lua
@@ -0,0 +1,8 @@
+local function check_role(k, _)
+   local role = string.format('roles.list[%q]', k)
+
+   need_string(role)
+end
+
+need_string('roles.default')
+need_table('roles.list', check_role)
diff --git a/package/gluon-luci-node-role/files/usr/lib/lua/luci/controller/admin/noderole.lua b/package/gluon-luci-node-role/files/usr/lib/lua/luci/controller/admin/noderole.lua
new file mode 100644
index 0000000000000000000000000000000000000000..dcde01f1b262fa3126debd5f3e5204aa27cce855
--- /dev/null
+++ b/package/gluon-luci-node-role/files/usr/lib/lua/luci/controller/admin/noderole.lua
@@ -0,0 +1,5 @@
+module("luci.controller.admin.noderole", package.seeall)
+
+function index()
+	entry({"admin", "noderole"}, cbi("admin/noderole"), "Verwendungszweck", 20)
+end
diff --git a/package/gluon-luci-node-role/files/usr/lib/lua/luci/model/cbi/admin/noderole.lua b/package/gluon-luci-node-role/files/usr/lib/lua/luci/model/cbi/admin/noderole.lua
new file mode 100644
index 0000000000000000000000000000000000000000..1d6d17982bbf8697e09d5f213fc9af40eb9f80ab
--- /dev/null
+++ b/package/gluon-luci-node-role/files/usr/lib/lua/luci/model/cbi/admin/noderole.lua
@@ -0,0 +1,36 @@
+local f, s, o
+local site = require 'gluon.site_config'
+local uci = luci.model.uci.cursor()
+local config = 'gluon-node-info'
+
+-- where to read the configuration from
+local role = uci:get(config, uci:get_first(config, "system"), "role")
+
+f = SimpleForm("role", "Verwendungszweck")
+f.reset = false
+f.template = "admin/expertmode"
+f.submit = "Fertig"
+
+s = f:section(SimpleSection, nil, [[
+Wenn dein Freifunk-Router eine besondere Rolle im Freifunk Netz einnimmt, kannst du diese hier angeben.
+Bringe bitte zuvor in Erfahrung welche Auswirkungen die zur Verfügung stehenden Rollen im Freifunk-Netz haben.
+Setze die Rolle nur, wenn du weißt was du machst.
+]])
+
+o = s:option(ListValue, "role", "Rolle")
+o.default = role
+o.rmempty = false
+for role, prettyname in pairs(site.roles.list) do
+  o:value(role, prettyname)
+end
+
+function f.handle(self, state, data)
+  if state == FORM_VALID then
+    uci:set(config, uci:get_first(config, "system"), "role", data.role)
+
+    uci:save(config)
+    uci:commit(config)
+  end
+end
+
+return f
diff --git a/package/gluon-luci-portconfig/Makefile b/package/gluon-luci-portconfig/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..92b3cbcfbe72eae9e51c0f3443be841601c257e9
--- /dev/null
+++ b/package/gluon-luci-portconfig/Makefile
@@ -0,0 +1,39 @@
+# Copyright (C) 2013 Nils Schneider <nils at nilsschneider.net>
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-portconfig
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-luci-portconfig
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Luci module for advanced ethernet port configuration
+  DEPENDS:=+gluon-luci-admin +gluon-mesh-batman-adv
+endef
+
+define Package/gluon-luci-portconfig/description
+	Luci module for advanced ethernet port configuration
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-portconfig/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-luci-portconfig))
diff --git a/package/gluon-luci-portconfig/files/usr/lib/lua/luci/controller/admin/portconfig.lua b/package/gluon-luci-portconfig/files/usr/lib/lua/luci/controller/admin/portconfig.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ff0e0af24cddaa76b9cd0833e3e28fdaf6f6021b
--- /dev/null
+++ b/package/gluon-luci-portconfig/files/usr/lib/lua/luci/controller/admin/portconfig.lua
@@ -0,0 +1,20 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2013 Nils Schneider <nils@nilsschneider.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]--
+
+module("luci.controller.admin.portconfig", package.seeall)
+
+function index()
+        entry({"admin", "portconfig"}, cbi("admin/portconfig"), _("Schnittstellen"), 20)
+end
+
diff --git a/package/gluon-luci-portconfig/files/usr/lib/lua/luci/model/cbi/admin/portconfig.lua b/package/gluon-luci-portconfig/files/usr/lib/lua/luci/model/cbi/admin/portconfig.lua
new file mode 100644
index 0000000000000000000000000000000000000000..7233d5c98b9bdd79ecd128877edf72851dfd7495
--- /dev/null
+++ b/package/gluon-luci-portconfig/files/usr/lib/lua/luci/model/cbi/admin/portconfig.lua
@@ -0,0 +1,133 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2014 Nils Schneider <nils@nilsschneider.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]--
+
+local uci = luci.model.uci.cursor()
+
+local wan = uci:get_all("network", "wan")
+local wan6 = uci:get_all("network", "wan6")
+local dns = uci:get_first("gluon-wan-dnsmasq", "static")
+
+local f = SimpleForm("portconfig", "WAN-Verbindung")
+f.template = "admin/expertmode"
+f.submit = "Speichern"
+f.reset = "Zurücksetzen"
+
+local s
+local o
+
+s = f:section(SimpleSection, nil, nil)
+
+o = s:option(ListValue, "ipv4", "IPv4")
+o:value("dhcp", "Automatisch (DHCP)")
+o:value("static", "Statisch")
+o:value("none", "Deaktiviert")
+o.default = wan.proto
+
+o = s:option(Value, "ipv4_addr", "IP-Adresse")
+o:depends("ipv4", "static")
+o.value = wan.ipaddr
+o.datatype = "ip4addr"
+o.rmempty = false
+
+o = s:option(Value, "ipv4_netmask", "Netzmaske")
+o:depends("ipv4", "static")
+o.value = wan.netmask or "255.255.255.0"
+o.datatype = "ip4addr"
+o.rmempty = false
+
+o = s:option(Value, "ipv4_gateway", "Gateway")
+o:depends("ipv4", "static")
+o.value = wan.gateway
+o.datatype = "ip4addr"
+o.rmempty = false
+
+
+s = f:section(SimpleSection, nil, nil)
+
+o = s:option(ListValue, "ipv6", "IPv6")
+o:value("dhcpv6", "Automatisch (RA/DHCPv6)")
+o:value("static", "Statisch")
+o:value("none", "Deaktiviert")
+o.default = wan6.proto
+
+o = s:option(Value, "ipv6_addr", "IP-Adresse")
+o:depends("ipv6", "static")
+o.value = wan6.ip6addr
+o.datatype = "ip6addr"
+o.rmempty = false
+
+o = s:option(Value, "ipv6_gateway", "Gateway")
+o:depends("ipv6", "static")
+o.value = wan6.ip6gw
+o.datatype = "ip6addr"
+o.rmempty = false
+
+
+if dns then
+  s = f:section(SimpleSection, nil, nil)
+
+  o = s:option(DynamicList, "dns", "Statische DNS-Server")
+  o:write(nil, uci:get("gluon-wan-dnsmasq", dns, "server"))
+  o.datatype = "ipaddr"
+end
+
+s = f:section(SimpleSection, nil, nil)
+
+o = s:option(Flag, "mesh_wan", "Mesh auf dem WAN-Port aktivieren")
+o.default = uci:get_bool("network", "mesh_wan", "auto") and o.enabled or o.disabled
+o.rmempty = false
+
+function f.handle(self, state, data)
+  if state == FORM_VALID then
+    uci:set("network", "wan", "proto", data.ipv4)
+    if data.ipv4 == "static" then
+      uci:set("network", "wan", "ipaddr", data.ipv4_addr)
+      uci:set("network", "wan", "netmask", data.ipv4_netmask)
+      uci:set("network", "wan", "gateway", data.ipv4_gateway)
+    else
+      uci:delete("network", "wan", "ipaddr")
+      uci:delete("network", "wan", "netmask")
+      uci:delete("network", "wan", "gateway")
+    end
+
+    uci:set("network", "wan6", "proto", data.ipv6)
+    if data.ipv6 == "static" then
+      uci:set("network", "wan6", "ip6addr", data.ipv6_addr)
+      uci:set("network", "wan6", "ip6gw", data.ipv6_gateway)
+    else
+      uci:delete("network", "wan6", "ip6addr")
+      uci:delete("network", "wan6", "ip6gw")
+    end
+
+    uci:set("network", "mesh_wan", "auto", data.mesh_wan)
+
+    uci:save("network")
+    uci:commit("network")
+
+    if dns then
+      if #data.dns > 0 then
+        uci:set("gluon-wan-dnsmasq", dns, "server", data.dns)
+      else
+        uci:delete("gluon-wan-dnsmasq", dns, "server")
+      end
+
+      uci:save("gluon-wan-dnsmasq")
+      uci:commit("gluon-wan-dnsmasq")
+    end
+  end
+
+  return true
+end
+
+return f
diff --git a/package/gluon-luci-private-wifi/Makefile b/package/gluon-luci-private-wifi/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0d5c4619cbea2bf94416b4dcabacaf6cd2dfa52c
--- /dev/null
+++ b/package/gluon-luci-private-wifi/Makefile
@@ -0,0 +1,32 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-private-wifi
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-luci-private-wifi
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  DEPENDS:=+gluon-luci-admin
+  TITLE:=UI for activating a private WLAN
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-private-wifi/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-luci-private-wifi))
diff --git a/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/controller/admin/privatewifi.lua b/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/controller/admin/privatewifi.lua
new file mode 100644
index 0000000000000000000000000000000000000000..0b10b4e92d9f02f35786d642088376c3094f3587
--- /dev/null
+++ b/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/controller/admin/privatewifi.lua
@@ -0,0 +1,5 @@
+module("luci.controller.admin.privatewifi", package.seeall)
+
+function index()
+	entry({"admin", "privatewifi"}, cbi("admin/privatewifi"), "Privates WLAN", 10)
+end
diff --git a/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/model/cbi/admin/privatewifi.lua b/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/model/cbi/admin/privatewifi.lua
new file mode 100644
index 0000000000000000000000000000000000000000..b915731012de2d0c8218b480498bea8eff62860f
--- /dev/null
+++ b/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/model/cbi/admin/privatewifi.lua
@@ -0,0 +1,63 @@
+local f, s, o, ssid
+local uci = luci.model.uci.cursor()
+local config = 'wireless'
+
+-- where to read the configuration from
+local primary_iface = 'wan_radio0'
+local ssid = uci:get(config, primary_iface, "ssid")
+
+f = SimpleForm("wifi", "Privates WLAN")
+f.reset = false
+f.template = "admin/expertmode"
+f.submit = "Fertig"
+
+s = f:section(SimpleSection, nil, [[
+Dein Freifunk-Router kann ebenfalls die Reichweite deines privaten Netzes erweitern.
+Hierfür wird der WAN-Port mit einem seperaten WLAN gebridged.
+Diese Funktionalität ist völlig unabhängig von Freifunk.
+Beachte, dass du nicht gleichzeitig das Meshen über den WAN Port aktiviert haben solltest.
+]])
+
+o = s:option(Flag, "enabled", "Aktiviert")
+o.default = (ssid and not uci:get_bool(config, primary_iface, "disabled")) and o.enabled or o.disabled
+o.rmempty = false
+
+o = s:option(Value, "ssid", "Name (SSID)")
+o.default = ssid
+
+o = s:option(Value, "key", "Schlüssel", "8-63 Zeichen")
+o.datatype = "wpakey"
+o.default = uci:get(config, primary_iface, "key")
+
+function f.handle(self, state, data)
+  if state == FORM_VALID then
+    uci:foreach(config, "wifi-device",
+      function(s)
+        local device = s['.name']
+        local name   = "wan_" .. device
+
+        if data.enabled == '1' then
+          -- set up WAN wifi-iface
+          local t      = uci:get_all(config, name) or {}
+
+          t.device     = device
+          t.network    = "wan"
+          t.mode       = 'ap'
+          t.encryption = 'psk2'
+          t.ssid       = data.ssid
+          t.key        = data.key
+          t.disabled   = "false"
+
+          uci:section(config, "wifi-iface", name, t)
+        else
+          -- disable WAN wifi-iface
+          uci:set(config, name, "disabled", "true")
+        end
+    end)
+
+    uci:save(config)
+    uci:commit(config)
+  end
+end
+
+return f
diff --git a/package/gluon-luci-theme/Makefile b/package/gluon-luci-theme/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4fc947c46ac3498c656445c31b67ec20ef8365ff
--- /dev/null
+++ b/package/gluon-luci-theme/Makefile
@@ -0,0 +1,39 @@
+# Copyright (C) 2013 Nils Schneider <nils at nilsschneider.net>
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-theme
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-luci-theme
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Luci theme for Gluon
+  DEPENDS:=
+endef
+
+define Package/gluon-luci-theme/description
+	Luci based config mode
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-theme/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-luci-theme))
diff --git a/package/gluon-luci-theme/files/etc/uci-defaults/luci-theme-gluon b/package/gluon-luci-theme/files/etc/uci-defaults/luci-theme-gluon
new file mode 100755
index 0000000000000000000000000000000000000000..795bd186ae160441ac77bf902eb5ae01163e1bf7
--- /dev/null
+++ b/package/gluon-luci-theme/files/etc/uci-defaults/luci-theme-gluon
@@ -0,0 +1,6 @@
+#!/bin/sh
+uci batch <<-EOF
+	set luci.themes.Gluon=/luci-static/gluon
+        commit luci
+EOF
+	
diff --git a/package/gluon-luci-theme/files/usr/lib/lua/luci/view/themes/gluon/footer.htm b/package/gluon-luci-theme/files/usr/lib/lua/luci/view/themes/gluon/footer.htm
new file mode 100644
index 0000000000000000000000000000000000000000..6b7090307a0f447b2a6192678972ec8fa420e0c3
--- /dev/null
+++ b/package/gluon-luci-theme/files/usr/lib/lua/luci/view/themes/gluon/footer.htm
@@ -0,0 +1,19 @@
+<%#
+LuCI - Lua Configuration Interface
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+
+-%>
+</div>
+</div>
+
+</body>
+</html>
diff --git a/package/gluon-luci-theme/files/usr/lib/lua/luci/view/themes/gluon/header.htm b/package/gluon-luci-theme/files/usr/lib/lua/luci/view/themes/gluon/header.htm
new file mode 100644
index 0000000000000000000000000000000000000000..730647bc2573f565b35eac62677e1a6e38f00535
--- /dev/null
+++ b/package/gluon-luci-theme/files/usr/lib/lua/luci/view/themes/gluon/header.htm
@@ -0,0 +1,169 @@
+<%#
+LuCI - Lua Configuration Interface
+Copyright 2008 Steven Barth <steven@midlink.org>
+Copyright 2008-2010 Jo-Philipp Wich <xm@subsignal.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+
+-%>
+<%
+	local sys  = require "luci.sys"
+	local http = require "luci.http"
+	local disp = require "luci.dispatcher"
+	local fs   = require "nixio.fs"
+
+	local hostname = sys.hostname()
+	local release = fs.readfile("/lib/gluon/release")
+	local load1, load5, load15 = sys.loadavg()
+
+	local request  = disp.context.path
+	local request2 = disp.context.request
+
+	local category = request[1]
+	local cattree  = category and disp.node(category)
+
+	local leaf = request2[#request2]
+
+	local tree = disp.node()
+	local node = disp.context.dispatched
+
+	local categories = disp.node_childs(tree)
+
+	local c = tree
+	local i, r
+
+	-- tag all nodes leading to this page
+	for i, r in ipairs(request) do
+		if c.nodes and c.nodes[r] then
+			c = c.nodes[r]
+			c._menu_selected = true
+		end
+	end
+
+	http.prepare_content("application/xhtml+xml")
+
+	local function nodeurl(prefix, name, query)
+		local url = controller .. prefix .. name .. "/"
+		if query then
+			url = url .. http.build_querystring(query)
+		end
+		return pcdata(url)
+	end
+
+	local function subtree(prefix, node, level)
+		if not level then
+			level = 1
+		end
+
+		local childs = disp.node_childs(node)
+		if #childs > 0 then
+%>
+	<div class="tabmenu<%=level%>">
+	<ul class="tabmenu l<%=level%>">
+		<%
+			local selected_node
+			local selected_name
+			local i, v
+
+			for i, v in ipairs(childs) do
+				local nnode = node.nodes[v]
+				if nnode._menu_selected then
+					selected_node = nnode
+					selected_name = v
+				end
+		%>
+			<li class="tabmenu-item-<%=v%><% if nnode._menu_selected or (node.leaf and v == leaf) then %> active<% end %>">
+				<a href="<%=nodeurl(prefix, v, nnode.query)%>"><%=striptags(translate(nnode.title))%></a>
+			</li>
+		<%
+			end
+		%>
+	</ul>
+	<br style="clear:both" />
+<%
+			if selected_node then
+				subtree(prefix .. selected_name .. "/", selected_node, level + 1)
+			end
+%>
+	</div>
+<%
+		end
+	end
+-%>
+
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%=luci.i18n.context.lang%>" lang="<%=luci.i18n.context.lang%>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta http-equiv="Content-Script-Type" content="text/javascript" />
+<link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />
+<% if node and node.css then %><link rel="stylesheet" type="text/css" media="screen" href="<%=resource%>/<%=node.css%>" />
+<% end -%>
+<% if css then %><style title="text/css">
+<%= css %>
+</style>
+<% end -%>
+<script type="text/javascript" src="<%=resource%>/xhr.js"></script>
+<title><%=striptags( hostname .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>
+</head>
+<body class="lang_<%=luci.i18n.context.lang%>">
+
+<div id="menubar">
+<div class="hostinfo">
+	<%=hostname%>
+	<% if release then %>
+	/ <%=release%>
+	<% end %>
+	<span id="xhr_poll_status" style="display:none" onclick="XHR.running() ? XHR.halt() : XHR.run()">
+		| <%:Auto Refresh%>:
+		<span id="xhr_poll_status_on"><%:on%></span>
+		<span id="xhr_poll_status_off" style="display:none"><%:off%></span>
+	</span>
+</div>
+
+<% if #categories > 1 then %>
+	<ul id="modemenu">
+		<% for i, r in ipairs(categories) do %>
+			<li><a<% if request[1] == r then %> class="active"<%end%> href="<%=controller%>/<%=r%>/"><%=striptags(translate(tree.nodes[r].title))%></a></li>
+		<% end %>
+	</ul>
+<% end %>
+
+<%
+if tree.nodes[category] and tree.nodes[category].ucidata then
+	local ucic = 0
+	for i, j in pairs(require("luci.model.uci").cursor():changes()) do
+		for k, l in pairs(j) do
+			for m, n in pairs(l) do
+				ucic = ucic + 1;
+			end
+		end
+	end
+-%>
+<div id="savemenu">
+	<% if ucic > 0 then %>
+		<a class="warning" href="<%=controller%>/<%=category%>/uci/changes/?redir=<%=luci.http.urlencode(luci.http.formvalue("redir") or REQUEST_URI)%>"><%:Unsaved Changes%>: <%=ucic%></a>
+	<%- else -%>
+		<a href="#"><%:Changes%>: 0</a>
+	<% end -%>
+</div><% end %>
+
+</div>
+
+<div id="maincontainer">
+  <% if category then subtree("/" .. category .. "/", cattree) end %>
+
+	<div id="maincontent">
+		<noscript>
+			<div class="errorbox">
+				<strong><%:Java Script required!%></strong><br />
+				<%:You must enable Java Script in your browser or LuCI will not work properly.%>
+			</div>
+		</noscript>
diff --git a/package/gluon-luci-theme/files/www/luci-static/gluon/cascade.css b/package/gluon-luci-theme/files/www/luci-static/gluon/cascade.css
new file mode 100644
index 0000000000000000000000000000000000000000..ba2fb8881c5bba16ddbfd84da59e55e8bf383c2b
--- /dev/null
+++ b/package/gluon-luci-theme/files/www/luci-static/gluon/cascade.css
@@ -0,0 +1 @@
+@charset "UTF-8";.lang_he{direction:RTL;unicode-bidi:embed}.hidden{display:none}html{min-height:100%;height:auto;position:relative}body,input,select,option{font-family:'Open Sans', Arial, sans-serif;font-size:12pt}body{color:#4d4e53;line-height:1.5em;margin:0;display:flex;flex-direction:column;min-height:100vh;background-color:#f3f3f3}a img{border:none;text-decoration:none}.tabmenu1{text-align:center}ul.tabmenu{list-style:none;padding:0;margin:2em 0;display:inline-flex}ul.tabmenu li{white-space:nowrap;margin:0 0.5em;padding:0;text-align:center}ul.tabmenu li a{display:block;text-decoration:none;padding:1em;margin:0;color:#333;border-radius:2em}ul.tabmenu li a:hover{background:#ffe9b3}ul.tabmenu li.active a{font-weight:bold;background:white;color:#333}abbr,acronym{font-style:normal;font-variant:normal}abbr[title],acronym[title]{border-bottom:1px dotted;cursor:help}a:link abbr[title],a:visited abbr[title],a:link acronym[title],a:visited acronym[title]{cursor:pointer}code{font-family:monospace;white-space:pre}#maincontent ul{margin-left:2em}.warning{color:red;background-color:white;font-weight:bold}.clear{clear:both}.error{color:#ff0000;background-color:white}div.hostinfo{margin:0;padding:0;font-size:80%;padding:0.5em;flex:1;font-weight:bold}#xhr_poll_status{cursor:pointer}#xhr_poll_status #xhr_poll_status_off{font-weight:bold;color:#FF0000}#xhr_poll_status #xhr_poll_status_on{font-weight:bold;color:#00FF00}#menubar{display:flex;background:#dc0067;color:#ffffff}#menubar .warning{color:red;background-color:#557788}#menubar a:link,#menubar a:visited{position:relative;display:block;padding:0.5em;text-decoration:none;font-size:80%;font-weight:normal;color:white}#menubar a:link:hover,#menubar a:visited:hover,#menubar a:link:focus,#menubar a:visited:focus{background:#ffb400;color:black}#menubar a:link.active,#menubar a:visited.active{background:#ffb400;color:black;font-weight:bold}#menubar a:link.warning,#menubar a:visited.warning{background:#000000;color:red;font-weight:bold}#modemenu{list-style:none;margin:0;padding:0}#modemenu li{display:inline-block}.lang_de #submenu_admin_uci{width:12em}.lang_ru #submenu_admin_uci{width:11.5em}textarea#syslog{width:98%;min-height:500px;border:3px solid #cccccc;padding:5px;font-family:monospace}#maincontent{padding:0 1em 2em;max-width:60em;min-width:40em;margin:1em auto}.lang_he #maincontent{direction:rtl}#maincontent p{margin-bottom:1em}.cbi-section{margin:0;padding:0;border:none}.cbi-section legend{font-size:1.4em;font-weight:bold;color:#dc0067;position:relative;padding:0;margin-bottom:0.5em}.cbi-section h2{margin:0em 0 0.5em -0.5em !important}.cbi-section h3{text-decoration:none !important;font-weight:bold !important;color:#555555 !important;margin:0.25em !important;font-size:100% !important}.cbi-section-descr{margin-bottom:2em}.cbi-title-ref{color:inherit;text-decoration:none;padding-right:18px;background:url("../resources/cbi/link.gif") no-repeat scroll right center;background-color:inherit}ul.cbi-apply{font-size:90%}input:-webkit-input-placeholder{color:#AAAAAA}input:-moz-placeholder{color:#AAAAAA}input:-ms-input-placeholder{color:#AAAAAA}input[type=checkbox]{display:none}input[type=checkbox]+label{display:inline-block;width:1em;height:1em;margin:0}input[type=checkbox]:checked+label:after{content:'✔';color:#dc0067;vertical-align:middle;position:absolute;top:50%;left:0;margin-top:-0.5em;width:100%;text-align:center;font-size:1.7em}input[type=submit],input[type=reset],input[type=image],input[type=button]{cursor:pointer}select,input,textarea,input[type=checkbox]+label{color:#003247;border:none;background:#ffe199;border-radius:3pt;padding:0.5em}input[type=image]{border:none}select,input[type=text],input[type=password]{width:20em}td select,td input[type=text],td input[type=password]{width:99%}img.cbi-image-button{cursor:pointer;margin:0 2px;vertical-align:middle}input.cbi-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-size:100%;padding:0.5em 1em;color:rgba(0,0,0,0.8);border:none transparent;background-color:#E6E6E6;text-decoration:none;border-radius:2px;-webkit-transition:0.1s linear -webkit-box-shadow;-moz-transition:0.1s linear -moz-box-shadow;-ms-transition:0.1s linear box-shadow;-o-transition:0.1s linear box-shadow;transition:0.1s linear box-shadow;background-repeat:no-repeat}input.cbi-button::-moz-focus-inner{padding:0;border:0}input.cbi-button:active{box-shadow:0 0 0 1px rgba(0,0,0,0.15) inset,0 0 6px rgba(0,0,0,0.2) inset}input.cbi-button:focus{outline:0}input.cbi-button:hover,input.cbi-button:focus{background-image:-webkit-gradient(linear, 0 0, 0 100%, from(transparent), color-stop(40%, rgba(0,0,0,0.05)), to(rgba(0,0,0,0.1)));background-image:-webkit-linear-gradient(transparent, rgba(0,0,0,0.05) 40%, rgba(0,0,0,0.1));background-image:-moz-linear-gradient(top, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0.1));background-image:-o-linear-gradient(transparent, rgba(0,0,0,0.05) 40%, rgba(0,0,0,0.1));background-image:linear-gradient(transparent, rgba(0,0,0,0.05) 40%, rgba(0,0,0,0.1))}input.cbi-button[disabled]{border:none;background-image:none;opacity:0.40;cursor:not-allowed;box-shadow:none}input.cbi-input-user{background-image:url("../resources/cbi/user.gif");background-repeat:no-repeat;background-position:1px center;color:#000000;text-indent:17px}input.cbi-input-find,input.cbi-button-find{background-image:url("../resources/cbi/find.gif");color:#000000;padding-left:17px}input.cbi-input-reload{background-image:url("../resources/cbi/reload.gif");color:#000000;padding-left:17px}input.cbi-input-add,input.cbi-button-add{background-image:url("../resources/cbi/add.gif");color:#000000;padding-left:17px;padding-right:1px}input.cbi-input-fieldadd,input.cbi-button-fieldadd{background-image:url(../resources/cbi/fieldadd.gif);color:#000000;padding-left:17px;padding-right:1px}input.cbi-input-reset,input.cbi-button-reset{background-color:#e30;color:#fff}input.cbi-input-save,input.cbi-button-save{background-color:#009ee0;color:#fff}input.cbi-input-apply,input.cbi-button-apply{background-color:#009ee0;color:#fff}input.cbi-input-link,input.cbi-button-link{background-image:url("../resources/cbi/link.gif");color:#000000;padding-left:17px;padding-right:1px}input.cbi-input-download,input.cbi-button-download{background-image:url("../resources/cbi/download.gif");color:#000000;padding-left:17px;padding-right:1px}input.cbi-input-remove,div.cbi-section-remove input{background-image:url("../resources/cbi/remove.gif");color:#000000;padding-left:17px;padding-right:1px}input.cbi-button-up{background-image:url("../resources/cbi/up.gif");padding-left:11px;padding-right:1px}input.cbi-button-down{background-image:url("../resources/cbi/down.gif");padding-left:11px;padding-right:1px}input.cbi-button-edit{background-image:url("../resources/cbi/edit.gif");color:#000000;padding-left:17px;padding-right:1px}input.cbi-button-reload{background-image:url("../resources/cbi/reload.gif");color:#000000;padding-left:17px;padding-right:1px}input.cbi-button-remove{background-image:url("../resources/cbi/remove.gif");color:#000000;padding-left:17px;padding-right:1px}.cbi-input-invalid{background:#e30 !important;color:white}div.cbi-section-remove input{border-bottom:none}textarea{margin-left:-1px;margin-bottom:0.5em}form>div>input[type=submit],form>div>input[type=reset]{margin-left:0.5em}table td,table th{color:#000000}table.smalltext{background:#f5f5f5;color:#000000;border-top:1px solid #666666;border-right:1px solid #666666;border-bottom:1px solid #666666;font-size:90%;width:80%;margin-left:auto;margin-right:auto;border-collapse:collapse}table.smalltext tr:hover td{background-color:#bbddee;color:#000000}table.smalltext tr th{padding:0 0.25em;border-left:1px solid #666666;text-align:left}table.smalltext tr td{padding:0 0.25em;border-top:1px solid #666666;border-left:1px solid #666666}table.cbi-section-table .cbi-rowstyle-1{background-color:#eeeeff;color:#000000}table.cbi-section-table .cbi-rowstyle-1:hover,table.cbi-section-table .cbi-rowstyle-2:hover{background-color:#b2c8d4;color:#000000}table.cbi-section-table .cbi-section-table-cell{padding:3px;white-space:nowrap}.cbi-section .cbi-rowstyle-1 h3{background-color:#eeeeff;color:#555555}.cbi-rowstyle-2{color:#000000}div.cbi-value{display:flex;flex-direction:row;margin-bottom:0.5em}.cbi-value-title{flex:2;text-align:right;padding-right:1em;font-weight:bold}div.cbi-value-field{flex:3;position:relative}div.cbi-value-field input,div.cbi-value-field select,div.cbi-value-field input+label{position:relative;top:-0.39em}div.cbi-value-description{font-size:8pt}div.cbi-section-create{clear:left;white-space:nowrap;vertical-align:top}div.cbi-section-create .cbi-button{margin:0.25em}input.cbi-section-create-name{margin-right:-0.25em}div.cbi-map-descr{margin-bottom:1em}.cbi-map-descr:empty,.cbi-section-descr:empty{display:none}.cbi-map-descr,.cbi-section-descr,.cbi-page-actions{padding:1em;background:#ececec}.cbi-page-actions{text-align:right;display:flex;display:-moz-flex;-moz-flex-flow:row-reverse;flex-flow:row-reverse}div.cbi-optionals{padding:0.25em;border-bottom:1px dotted #bbbbbb}div.cbi-section-remove{float:right}.cbi-section-node{clear:both;position:relative;border:none}.cbi-section-node-tabbed{border-top-left-radius:0}.cbi-section-node .cbi-value-last{border-bottom:none}.cbi-section-node table div{padding-bottom:0;border-bottom:none}.cbi-section-node div.cbi-section-table-row{margin:0.25em}table.cbi-section-table{width:100%;font-size:95%}table.cbi-section-table th,table.cbi-section-table td{text-align:center}tr.cbi-section-table-descr th{font-weight:normal;font-size:90%;vertical-align:top}td.cbi-section-table-optionals{text-align:left !important;padding-top:1em}.cbi-value-helpicon img{display:none}div.cbi-error{font-size:95%;font-weight:bold;color:#ff0000;background-color:#ffffff}td.cbi-value-error{border-color:red}.cbi-value-error input,.cbi-value-error select{background-color:#ffcccc}.cbi-section-error{color:red;background-color:white;font-size:95%;border:1px dotted red;margin:3px;padding:3px}.cbi-value-field var{color:#2222FF}ul.cbi-tabmenu{padding:3px 0;margin-left:0 !important;list-style-type:none;position:relative;z-index:10;top:4px;line-height:20px}ul.cbi-tabmenu li.cbi-tab,ul.cbi-tabmenu li.cbi-tab-disabled{display:inline;margin:0}ul.cbi-tabmenu li.cbi-tab a,ul.cbi-tabmenu li.cbi-tab-disabled a{text-decoration:none;padding:3px 7px;margin-right:3px;border:1px solid #BBBBBB;border-bottom:none;border-radius:3px 3px 0 0;background-color:#EEEEEE;color:#BBBBBB}ul.cbi-tabmenu li.cbi-tab-highlighted a{color:#000000;background-color:#FFEEAA}ul.cbi-tabmenu li a:hover{color:#000000}ul.cbi-tabmenu li.cbi-tab a{padding-top:4px;color:#000000;background-color:#FFFFFF}div.cbi-tab-descr{background-image:url(/luci-static/resources/cbi/help.gif);background-position:0.25em 50%;background-repeat:no-repeat;border-bottom:1px solid #CCCCCC;margin:0.25em 0.25em 2em;padding:0.5em 0.5em 0.5em 2em}.left{text-align:left !important}.right{text-align:right !important}.luci{position:absolute;bottom:0;left:1em;height:1.5em;font-size:80%}.luci a:link,.luci a:visited{background-color:transparent;color:#666666;text-decoration:none;font-size:70%}.inline{display:inline}.error500{white-space:normal;border:1px dotted #ff0000;background-color:#ffffff;color:#000000;padding:0.5em}.errorbox{border:1px solid #FF0000;background-color:#FFCCCC;padding:5px;margin-bottom:5px}.errorbox a{color:#000000 !important}.ifacebox{background-color:#FFFFFF;border:1px solid #CCCCCC;margin:0 10px;text-align:center;white-space:nowrap}.ifacebox .ifacebox-head{border-bottom:1px solid #CCCCCC;padding:2px}.ifacebox .ifacebox-body{padding:2px}.ifacebadge{background-color:#FFFFFF;border:1px solid #CCCCCC;padding:2px;margin-left:2px;display:inline-block}.ifacebadge-active{border-color:#000000;font-weight:bold}.zonebadge{padding:2px;display:inline-block;white-space:nowrap;cursor:pointer}.zonebadge em,.zonebadge strong{margin:3px;display:inline-block}.zonebadge input{width:6em;height:1.5em}.zonebadge-empty{border:1px dashed #AAAAAA;color:#AAAAAA;font-style:italic;font-size:smaller}.uci-change-list{font-family:monospace}.uci-change-list ins,.uci-change-legend-label ins{text-decoration:none;border:1px solid #00FF00;background-color:#CCFFCC;display:block;padding:2px}.uci-change-list del,.uci-change-legend-label del{text-decoration:none;border:1px solid #FF0000;background-color:#FFCCCC;display:block;font-style:normal;padding:2px}.uci-change-list var,.uci-change-legend-label var{text-decoration:none;border:1px solid #CCCCCC;background-color:#EEEEEE;display:block;font-style:normal;padding:2px}.uci-change-list var ins,.uci-change-list var del{border:none;white-space:pre;font-style:normal;padding:0px}.uci-change-legend{padding:5px}.uci-change-legend-label{width:150px;float:left;font-size:80%}.uci-change-legend-label>ins,.uci-change-legend-label>del,.uci-change-legend-label>var{float:left;margin-right:4px;width:10px;height:10px;display:block}.uci-change-legend-label var ins,.uci-change-legend-label var del{line-height:6px;border:none}.cbi-input-password+img{display:none}.the-key{text-align:left;font-size:1.4em;background:#ffe9b3;border:3pt dashed #dc0067;margin-bottom:0.5em;padding:0.5em}
diff --git a/package/gluon-luci-theme/sass/cascade.scss b/package/gluon-luci-theme/sass/cascade.scss
new file mode 100644
index 0000000000000000000000000000000000000000..80e407bebe3f4b4d1cb20bae8e2f9f319a64818e
--- /dev/null
+++ b/package/gluon-luci-theme/sass/cascade.scss
@@ -0,0 +1,1019 @@
+/* ATTENTION: This file is not compiled when building gluon.
+   The compiled version is at ../files/www/luci-static/gluon/cascade.css
+
+   Use sass like this to update it:
+
+   sass cascade.scss ../files/www/luci-static/gluon/cascade.css
+
+   When commiting changes to this file make sure to commit the respective
+   changes to the compilid version within the same commit!
+   */
+
+@charset "utf-8";
+
+$ffyellow:  #ffb400;
+$ffmagenta: #dc0067;
+$ffzusatz:  #009ee0;
+$red:       #ee3300;
+
+@mixin button {
+  &::-moz-focus-inner {
+    padding: 0;
+    border: 0;
+  }
+
+  display: inline-block;
+  zoom: 1;
+  line-height: normal;
+  white-space: nowrap;
+  vertical-align: baseline;
+  text-align: center;
+  cursor: pointer;
+  -webkit-user-drag: none;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+
+
+  font-size: 100%;
+  padding: 0.5em 1em;
+  color: rgba(0, 0, 0, 0.80);
+  border: none rgba(0, 0, 0, 0);
+  background-color: #E6E6E6;
+  text-decoration: none;
+  border-radius: 2px;
+
+  /* Transitions */
+  -webkit-transition: 0.1s linear -webkit-box-shadow;
+  -moz-transition: 0.1s linear -moz-box-shadow;
+  -ms-transition: 0.1s linear box-shadow;
+  -o-transition: 0.1s linear box-shadow;
+  transition: 0.1s linear box-shadow;
+
+  &:active {
+    box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset;
+  }
+
+  &:focus {
+    outline: 0;
+  }
+
+  &:hover, &:focus {
+    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(transparent), color-stop(40%, rgba(0,0,0, 0.05)), to(rgba(0,0,0, 0.10)));
+    background-image: -webkit-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10));
+    background-image: -moz-linear-gradient(top, rgba(0,0,0, 0.05) 0%, rgba(0,0,0, 0.10));
+    background-image: -o-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10));
+    background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10));
+  }
+
+  &[disabled] {
+    border: none;
+    background-image: none;
+    opacity: 0.40;
+    cursor: not-allowed;
+    box-shadow: none;
+  }
+}
+
+@mixin button-primary {
+  background-color: $ffzusatz;
+  color: #fff;
+}
+
+.lang_he {
+  direction: RTL;
+  unicode-bidi: embed;
+}
+
+.hidden {
+  display: none;
+}
+
+html {
+  min-height: 100%;
+  height: auto;
+  position:relative;
+}
+
+body, input, select, option {
+  font-family: 'Open Sans', Arial, sans-serif;
+  font-size: 12pt;
+}
+
+body {
+  color: rgb(77, 78, 83);
+  line-height: 1.5em;
+  margin: 0;
+  display: flex;
+  flex-direction: column;
+  min-height: 100vh;
+  background-color: #f3f3f3;
+}
+
+a img {
+  border: none;
+  text-decoration: none;
+}
+
+.tabmenu1 {
+  text-align: center;
+}
+
+ul.tabmenu {
+  list-style: none;
+  padding: 0;
+  margin: 2em 0;
+  display: inline-flex;
+}
+
+ul.tabmenu li {
+  white-space: nowrap;
+  margin: 0 0.5em;
+  padding: 0;
+  text-align: center;
+
+  a {
+    display: block;
+    text-decoration: none;
+    padding: 1em;
+    margin: 0;
+    color: #333;
+    border-radius: 2em;
+
+    &:hover {
+      background: lighten($ffyellow, 35);
+    }
+  }
+
+  &.active a {
+    font-weight: bold;
+    background: white;
+    color: #333;
+  }
+}
+
+abbr,
+acronym {
+  font-style: normal;
+  font-variant: normal;
+}
+
+abbr[title],
+acronym[title] {
+  border-bottom: 1px dotted;
+  cursor: help;
+}
+
+a:link abbr[title],
+a:visited abbr[title],
+a:link acronym[title],
+a:visited acronym[title] {
+  cursor: pointer;
+}
+
+code {
+  font-family: monospace;
+  white-space: pre;
+}
+
+#maincontent ul {
+  margin-left: 2em;
+}
+
+.warning {
+  color: red;
+  background-color: white;
+  font-weight: bold;
+}
+
+.clear {
+  clear: both;
+}
+
+.error {
+  color: #ff0000;
+  background-color: white;
+}
+
+div.hostinfo {
+  margin: 0;
+  padding: 0;
+  font-size: 80%;
+  padding: 0.5em;
+  flex: 1;
+  font-weight: bold;
+}
+
+#xhr_poll_status {
+  cursor: pointer;
+}
+
+#xhr_poll_status #xhr_poll_status_off {
+  font-weight: bold;
+  color: #FF0000;
+}
+
+#xhr_poll_status #xhr_poll_status_on {
+  font-weight: bold;
+  color: #00FF00;
+}
+
+#menubar {
+  display: flex;
+  background: $ffmagenta;
+  color: #ffffff;
+}
+
+#menubar .warning {
+  color: red;
+  background-color: #557788;
+}
+
+#menubar a:link,
+#menubar a:visited {
+  position: relative;
+  display: block;
+  padding: 0.5em;
+  text-decoration: none;
+  font-size: 80%;
+  font-weight: normal;
+  color: white;
+}
+
+#menubar a:link:hover,
+#menubar a:visited:hover,
+#menubar a:link:focus,
+#menubar a:visited:focus {
+  background: $ffyellow;
+  color: black;
+}
+
+#menubar a:link.active,
+#menubar a:visited.active {
+  background: $ffyellow;
+  color: black;
+  font-weight: bold;
+}
+
+#menubar a:link.warning,
+#menubar a:visited.warning {
+  background: #000000;
+  color: red;
+  font-weight: bold;
+}
+
+#modemenu {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+
+#modemenu li {
+  display: inline-block;
+}
+
+#savemenu {
+}
+
+.lang_he #savemenu {
+}
+
+.lang_de #submenu_admin_uci {
+  width: 12em;
+}
+
+.lang_ru #submenu_admin_uci {
+  width: 11.5em;
+}
+
+textarea#syslog {
+  width: 98%;
+  min-height: 500px;
+  border: 3px solid #cccccc;
+  padding: 5px;
+  font-family: monospace;
+}
+
+#maincontent {
+  padding: 0 1em 2em;
+  max-width: 60em;
+  min-width: 40em;
+  margin: 1em auto;
+}
+
+.lang_he #maincontent {
+  direction: rtl;
+}
+
+#maincontent h2 {
+}
+
+#maincontent h3 {
+}
+
+#maincontent p {
+  margin-bottom: 1em;
+}
+
+.cbi-section {
+  margin: 0;
+  padding: 0;
+  border: none;
+}
+
+.cbi-section legend {
+  font-size: 1.4em;
+  font-weight: bold;
+  color: $ffmagenta; 
+  position: relative;
+  padding: 0;
+  margin-bottom: 0.5em;
+}
+
+.cbi-section h2 {
+  margin: 0em 0 0.5em -0.5em !important;
+}
+
+.cbi-section h3 {
+  text-decoration: none !important;
+  font-weight: bold !important;
+  color: #555555 !important;
+  margin: 0.25em !important;
+  font-size: 100% !important;
+}
+
+.cbi-section-descr {
+  margin-bottom: 2em;
+}
+
+.cbi-title-ref {
+  color: inherit;
+  text-decoration: none;
+  padding-right: 18px;
+  background: url('../resources/cbi/link.gif') no-repeat scroll right center;
+  background-color: inherit;
+}
+
+ul.cbi-apply {
+  font-size: 90%;
+}
+
+input:-webkit-input-placeholder {
+  color: #AAAAAA;
+}
+
+input:-moz-placeholder {
+  color: #AAAAAA;
+}
+
+input:-ms-input-placeholder {
+  color: #AAAAAA;
+}
+
+input[type=checkbox] {
+  display: none;
+
+  & + label {
+    display: inline-block;
+    width: 1em;
+    height: 1em;
+    margin: 0;
+  }
+
+  &:checked + label:after {
+    content: '✔';
+    color: $ffmagenta;
+    vertical-align: middle;
+    position: absolute;
+    top: 50%;
+    left: 0;
+    margin-top: -0.5em;
+    width: 100%;
+    text-align: center;
+    font-size: 1.7em;
+  }
+}
+
+input[type=submit],
+input[type=reset],
+input[type=image],
+input[type=button] {
+  cursor: pointer;
+}
+
+select,
+input,
+textarea,
+input[type=checkbox] + label {
+  color: darken($ffzusatz, 30);
+  border: none;
+  background: lighten($ffyellow, 30);
+  border-radius: 3pt;
+  padding: 0.5em;
+}
+
+input[type=image] {
+  border: none;
+}
+
+select,
+input[type=text],
+input[type=password] {
+  width: 20em;
+}
+
+td select,
+td input[type=text],
+td input[type=password] {
+  width: 99%;
+}
+
+img.cbi-image-button {
+  cursor: pointer;
+  margin: 0 2px;
+  vertical-align: middle;
+}
+
+input.cbi-button {
+  @include button;
+
+  background-repeat: no-repeat;
+}
+
+input.cbi-input-user {
+  background-image: url('../resources/cbi/user.gif');
+  background-repeat: no-repeat;
+  background-position: 1px center;
+  color: #000000;
+  text-indent: 17px;
+}
+
+input.cbi-input-find,
+input.cbi-button-find {
+  background-image: url('../resources/cbi/find.gif');
+  color: #000000;
+  padding-left: 17px;
+}
+
+input.cbi-input-reload {
+  background-image: url('../resources/cbi/reload.gif');
+  color: #000000;
+  padding-left: 17px;
+}
+
+input.cbi-input-add,
+input.cbi-button-add {
+  background-image: url('../resources/cbi/add.gif');
+  color: #000000;
+  padding-left: 17px;
+  padding-right: 1px;
+}
+
+input.cbi-input-fieldadd,
+input.cbi-button-fieldadd {
+  background-image: url(../resources/cbi/fieldadd.gif);
+  color: #000000;
+  padding-left: 17px;
+  padding-right: 1px;
+}
+
+input.cbi-input-reset,
+input.cbi-button-reset {
+  background-color: $red;
+  color: #fff;
+}
+
+input.cbi-input-save,
+input.cbi-button-save {
+  @include button-primary;
+}
+
+input.cbi-input-apply,
+input.cbi-button-apply {
+  @include button-primary;
+}
+
+input.cbi-input-link,
+input.cbi-button-link {
+  background-image: url('../resources/cbi/link.gif');
+  color: #000000;
+  padding-left: 17px;
+  padding-right: 1px;
+}
+
+input.cbi-input-download,
+input.cbi-button-download {
+  background-image: url('../resources/cbi/download.gif');
+  color: #000000;
+  padding-left: 17px;
+  padding-right: 1px;
+}
+
+input.cbi-input-remove,
+div.cbi-section-remove input {
+  background-image: url('../resources/cbi/remove.gif');
+  color: #000000;
+  padding-left: 17px;
+  padding-right: 1px;
+}
+
+input.cbi-button-up {
+  background-image: url('../resources/cbi/up.gif');
+  padding-left: 11px;
+  padding-right: 1px;
+}
+
+input.cbi-button-down {
+  background-image: url('../resources/cbi/down.gif');
+  padding-left: 11px;
+  padding-right: 1px;
+}
+
+input.cbi-button-edit {
+  background-image: url('../resources/cbi/edit.gif');
+  color: #000000;
+  padding-left: 17px;
+  padding-right: 1px;
+}
+
+input.cbi-button-reload {
+  background-image: url('../resources/cbi/reload.gif');
+  color: #000000;
+  padding-left: 17px;
+  padding-right: 1px;
+}
+
+input.cbi-button-remove {
+  background-image: url('../resources/cbi/remove.gif');
+  color: #000000;
+  padding-left: 17px;
+  padding-right: 1px;
+}
+
+.cbi-input-invalid {
+  background: $red !important;
+  color: white;
+}
+
+div.cbi-section-remove input {
+  border-bottom: none;
+}
+
+textarea {
+  margin-left: -1px;
+  margin-bottom: 0.5em;
+}
+
+form > div > input[type=submit],
+form > div > input[type=reset] {
+  margin-left: 0.5em;
+}
+
+table td,
+table th {
+  color: #000000;
+}
+
+table.smalltext {
+  background: #f5f5f5;
+  color: #000000;
+  border-top: 1px solid #666666;
+  border-right: 1px solid #666666;
+  border-bottom: 1px solid #666666;
+  font-size: 90%;
+  width: 80%;
+  margin-left: auto;
+  margin-right: auto;
+  border-collapse: collapse;
+}
+
+table.smalltext tr:hover td {
+  background-color: #bbddee;
+  color: #000000;
+}
+
+table.smalltext tr th {
+  padding: 0 0.25em;
+  border-left: 1px solid #666666;
+  text-align: left;
+}
+
+table.smalltext tr td {
+  padding: 0 0.25em;
+  border-top: 1px solid #666666;
+  border-left: 1px solid #666666;
+}
+
+table.cbi-section-table .cbi-rowstyle-1 {
+  background-color: #eeeeff;
+  color: #000000;
+}
+
+table.cbi-section-table .cbi-rowstyle-1:hover,
+table.cbi-section-table .cbi-rowstyle-2:hover {
+  background-color: #b2c8d4;
+  color: #000000;
+}
+
+table.cbi-section-table .cbi-section-table-cell {
+  padding: 3px;
+  white-space: nowrap;
+}
+
+.cbi-section .cbi-rowstyle-1 h3 {
+  background-color: #eeeeff;
+  color: #555555;
+}
+
+.cbi-rowstyle-2 {
+  color: #000000;
+}
+
+div.cbi-value {
+  display: flex;
+  flex-direction: row;
+  margin-bottom: 0.5em;
+}
+
+.cbi-value-title {
+  flex: 2;
+  text-align: right;
+  padding-right: 1em;
+  font-weight: bold;
+}
+
+div.cbi-value-field {
+  flex: 3;
+  position: relative;
+
+  input, select, input + label {
+    position: relative;
+    top: -0.39em;
+  }
+}
+
+div.cbi-value-description {
+  font-size: 8pt;
+}
+
+div.cbi-section-create {
+  clear: left;
+  white-space: nowrap;
+  vertical-align: top;
+}
+
+div.cbi-section-create .cbi-button {
+  margin: 0.25em;
+}
+
+input.cbi-section-create-name {
+  margin-right: -0.25em;
+}
+
+div.cbi-map-descr {
+  margin-bottom: 1em;
+}
+
+.cbi-map-descr:empty, .cbi-section-descr:empty {
+  display: none;
+}
+
+.cbi-map-descr, .cbi-section-descr, .cbi-page-actions {
+  padding: 1em;
+  background: #ececec;
+}
+
+.cbi-page-actions {
+  text-align: right;
+  display: flex;
+  display: -moz-flex;
+  -moz-flex-flow: row-reverse;
+  flex-flow: row-reverse;
+}
+
+div.cbi-optionals {
+  padding: 0.25em;
+  border-bottom: 1px dotted #bbbbbb;
+}
+
+div.cbi-section-remove {
+  float: right;
+}
+
+.cbi-section-node {
+  clear: both;
+  position: relative;
+  border: none;
+}
+
+.cbi-section-node-tabbed {
+  border-top-left-radius: 0;
+}
+
+.cbi-section-node .cbi-value-last {
+  border-bottom: none;
+}
+
+.cbi-section-node table div {
+  padding-bottom: 0;
+  border-bottom: none;
+}
+
+.cbi-section-node div.cbi-section-table-row {
+  margin: 0.25em;
+}
+
+table.cbi-section-table {
+  width: 100%;
+  font-size: 95%;
+}
+
+table.cbi-section-table th,
+table.cbi-section-table td {
+  text-align: center;
+}
+
+tr.cbi-section-table-descr th {
+  font-weight: normal;
+  font-size: 90%;
+  vertical-align: top;
+}
+
+td.cbi-section-table-optionals {
+  text-align: left !important;
+  padding-top: 1em;
+}
+
+.cbi-value-helpicon img {
+  display: none;
+}
+
+div.cbi-error {
+  font-size: 95%;
+  font-weight: bold;
+  color: #ff0000;
+  background-color: #ffffff;
+}
+
+td.cbi-value-error {
+  border-color: red;
+}
+
+.cbi-value-error input,
+.cbi-value-error select {
+  background-color: #ffcccc;
+}
+
+.cbi-section-error {
+  color: red;
+  background-color: white;
+  font-size: 95%;
+  border: 1px dotted red;
+  margin: 3px;
+  padding: 3px;
+}
+
+.cbi-value-field var {
+  color: #2222FF;
+}
+
+ul.cbi-tabmenu {
+  padding: 3px 0;
+  margin-left: 0 !important;
+  list-style-type: none;
+  position: relative;
+  z-index: 10;
+  top: 4px;
+  line-height: 20px;
+}
+
+ul.cbi-tabmenu li.cbi-tab,
+ul.cbi-tabmenu li.cbi-tab-disabled {
+  display: inline;
+  margin: 0;
+}
+
+ul.cbi-tabmenu li.cbi-tab a,
+ul.cbi-tabmenu li.cbi-tab-disabled a {
+  text-decoration: none;
+  padding: 3px 7px;
+  margin-right: 3px;
+  border: 1px solid #BBBBBB;
+  border-bottom: none;
+  border-radius: 3px 3px 0 0;
+  background-color: #EEEEEE;
+  color: #BBBBBB;
+}
+
+ul.cbi-tabmenu li.cbi-tab-highlighted a {
+  color: #000000;
+  background-color: #FFEEAA;
+}
+
+ul.cbi-tabmenu li a:hover {
+  color: #000000;
+}
+
+ul.cbi-tabmenu li.cbi-tab a {
+  padding-top: 4px;
+  color: #000000;
+  background-color: #FFFFFF;
+}
+
+div.cbi-tab-descr {
+  background-image: url(/luci-static/resources/cbi/help.gif);
+  background-position: 0.25em 50%;
+  background-repeat: no-repeat;
+  border-bottom: 1px solid #CCCCCC;
+  margin: 0.25em 0.25em 2em;
+  padding: 0.5em 0.5em 0.5em 2em;
+}
+
+.left {
+  text-align: left !important;
+}
+
+.right {
+  text-align: right !important;
+}
+
+.luci {
+  position: absolute;
+  bottom: 0;
+  left: 1em;
+  height: 1.5em;
+  font-size: 80%;
+}
+
+.luci a:link,
+.luci a:visited {
+  background-color: transparent;
+  color: #666666;
+  text-decoration: none;
+  font-size: 70%;
+}
+
+.inline {
+  display: inline;
+}
+
+.error500 {
+  white-space: normal;
+  border: 1px dotted #ff0000;
+  background-color: #ffffff;
+  color: #000000;
+  padding: 0.5em;
+}
+
+.errorbox {
+  border: 1px solid #FF0000;
+  background-color: #FFCCCC;
+  padding: 5px;
+  margin-bottom: 5px;
+}
+
+.errorbox a {
+  color: #000000 !important;
+}
+
+
+.ifacebox {
+  background-color: #FFFFFF;
+  border: 1px solid #CCCCCC;
+  margin: 0 10px;
+  text-align: center;
+  white-space: nowrap;
+}
+
+.ifacebox .ifacebox-head {
+  border-bottom: 1px solid #CCCCCC;
+  padding: 2px;
+}
+
+.ifacebox .ifacebox-body {
+  padding: 2px;
+}
+
+
+.ifacebadge {
+  background-color: #FFFFFF;
+  border: 1px solid #CCCCCC;
+  padding: 2px;
+  margin-left: 2px;
+  display: inline-block;
+}
+
+.ifacebadge-active {
+  border-color: #000000;
+  font-weight: bold;
+}
+
+
+.zonebadge {
+  padding: 2px;
+  display: inline-block;
+  white-space: nowrap;
+  cursor: pointer;
+}
+
+.zonebadge em,
+.zonebadge strong {
+  margin: 3px;
+  display: inline-block;
+}
+
+.zonebadge input {
+  width: 6em;
+  height: 1.5em;
+}
+
+.zonebadge-empty {
+  border: 1px dashed #AAAAAA;
+  color: #AAAAAA;
+  font-style: italic;
+  font-size: smaller;
+}
+
+
+.uci-change-list {
+  font-family: monospace;
+}
+
+.uci-change-list ins,
+.uci-change-legend-label ins {
+  text-decoration: none;
+  border: 1px solid #00FF00;
+  background-color: #CCFFCC;
+  display: block;
+  padding: 2px;
+}
+
+.uci-change-list del,
+.uci-change-legend-label del {
+  text-decoration: none;
+  border: 1px solid #FF0000;
+  background-color: #FFCCCC;
+  display: block;
+  font-style: normal;
+  padding: 2px;
+}
+
+.uci-change-list var,
+.uci-change-legend-label var {
+  text-decoration: none;
+  border: 1px solid #CCCCCC;
+  background-color: #EEEEEE;
+  display: block;
+  font-style: normal;
+  padding: 2px;
+}
+
+.uci-change-list var ins,
+.uci-change-list var del {
+  /*display: inline;*/
+  border: none;
+  white-space: pre;
+  font-style: normal;
+  padding: 0px;
+}
+
+.uci-change-legend {
+  padding: 5px;
+}
+
+.uci-change-legend-label {
+  width: 150px;
+  float: left;
+  font-size: 80%;
+}
+
+.uci-change-legend-label>ins,
+.uci-change-legend-label>del,
+.uci-change-legend-label>var {
+  float: left;
+  margin-right: 4px;
+  width: 10px;
+  height: 10px;
+  display: block;
+}
+
+.uci-change-legend-label var ins,
+.uci-change-legend-label var del {
+  line-height: 6px;
+  border: none;
+}
+
+// Hide show/hide password toggle image
+.cbi-input-password + img {
+  display: none;
+}
+
+.the-key {
+  text-align: left;
+  font-size: 1.4em;
+  background: lighten($ffyellow, 35);
+  border: 3pt dashed $ffmagenta;
+  margin-bottom: 0.5em;
+  padding: 0.5em
+}
diff --git a/package/gluon-mesh-batman-adv-14/Makefile b/package/gluon-mesh-batman-adv-14/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..be65cafab349773e4caf7ff502885d1acfb4ec17
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-14/Makefile
@@ -0,0 +1,32 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-mesh-batman-adv-14
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-mesh-batman-adv-14
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Support for batman-adv meshing (compat level 14)
+  DEPENDS:=+gluon-mesh-batman-adv-core +kmod-batman-adv-legacy
+  PROVIDES:=gluon-mesh-batman-adv
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-mesh-batman-adv-14/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-mesh-batman-adv-14))
diff --git a/package/gluon-mesh-batman-adv-14/files/lib/gluon/announce/nodeinfo.d/software/batman-adv/compat b/package/gluon-mesh-batman-adv-14/files/lib/gluon/announce/nodeinfo.d/software/batman-adv/compat
new file mode 100644
index 0000000000000000000000000000000000000000..841372371492c21b94f0b3693af57dd3a2e05b0f
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-14/files/lib/gluon/announce/nodeinfo.d/software/batman-adv/compat
@@ -0,0 +1 @@
+return 14
diff --git a/package/gluon-mesh-batman-adv-14/files/lib/gluon/upgrade/350-gluon-mesh-batman-adv-14 b/package/gluon-mesh-batman-adv-14/files/lib/gluon/upgrade/350-gluon-mesh-batman-adv-14
new file mode 100755
index 0000000000000000000000000000000000000000..b3ed1e880461d441c2e70d09d5f6585c93794423
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-14/files/lib/gluon/upgrade/350-gluon-mesh-batman-adv-14
@@ -0,0 +1,40 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+
+local uci = require('luci.model.uci').cursor()
+
+
+local function configure_mtu(radio, config)
+  local mesh = 'mesh_' .. radio
+
+  if config.mesh_vlan then
+    uci:set('network', mesh, 'mtu', 1532)
+    uci:set('network', mesh .. '_vlan', 'mtu', 1528)
+  else
+    uci:set('network', mesh, 'mtu', 1528)
+  end
+end
+
+
+local radios = {}
+
+uci:foreach('wireless', 'wifi-device',
+	    function(s)
+	      table.insert(radios, s['.name'])
+	    end
+)
+
+for _, radio in ipairs(radios) do
+	local hwmode = uci:get('wireless', radio, 'hwmode')
+
+	if hwmode == '11g' or hwmode == '11ng' then
+	  configure_mtu(radio, site.wifi24)
+	elseif hwmode == '11a' or hwmode == '11na' then
+	  configure_mtu(radio, site.wifi5)
+	end
+end
+
+
+uci:save('network')
+uci:commit('network')
diff --git a/package/gluon-mesh-batman-adv-15/Makefile b/package/gluon-mesh-batman-adv-15/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..14a39a61152e23a963839924da962d52feaa4ea2
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-15/Makefile
@@ -0,0 +1,32 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-mesh-batman-adv-15
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-mesh-batman-adv-15
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Support for batman-adv meshing (compat level 15)
+  DEPENDS:=+gluon-mesh-batman-adv-core +kmod-batman-adv +batctl
+  PROVIDES:=gluon-mesh-batman-adv
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-mesh-batman-adv-15/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-mesh-batman-adv-15))
diff --git a/package/gluon-mesh-batman-adv-15/files/lib/gluon/announce/nodeinfo.d/software/batman-adv/compat b/package/gluon-mesh-batman-adv-15/files/lib/gluon/announce/nodeinfo.d/software/batman-adv/compat
new file mode 100644
index 0000000000000000000000000000000000000000..d44224b379b649b30e7e72c436204f1f19c9c860
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-15/files/lib/gluon/announce/nodeinfo.d/software/batman-adv/compat
@@ -0,0 +1 @@
+return 15
diff --git a/package/gluon-mesh-batman-adv-15/files/lib/gluon/upgrade/350-gluon-mesh-batman-adv-15 b/package/gluon-mesh-batman-adv-15/files/lib/gluon/upgrade/350-gluon-mesh-batman-adv-15
new file mode 100755
index 0000000000000000000000000000000000000000..96f7d3103b55ca48106f125f8c48000875a61c1b
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-15/files/lib/gluon/upgrade/350-gluon-mesh-batman-adv-15
@@ -0,0 +1,40 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+
+local uci = require('luci.model.uci').cursor()
+
+
+local function configure_mtu(radio, config)
+  local mesh = 'mesh_' .. radio
+
+  if config.mesh_vlan then
+    uci:set('network', mesh, 'mtu', 1536)
+    uci:set('network', mesh .. '_vlan', 'mtu', 1532)
+  else
+    uci:set('network', mesh, 'mtu', 1532)
+  end
+end
+
+
+local radios = {}
+
+uci:foreach('wireless', 'wifi-device',
+	    function(s)
+	      table.insert(radios, s['.name'])
+	    end
+)
+
+for _, radio in ipairs(radios) do
+	local hwmode = uci:get('wireless', radio, 'hwmode')
+
+	if hwmode == '11g' or hwmode == '11ng' then
+	  configure_mtu(radio, site.wifi24)
+	elseif hwmode == '11a' or hwmode == '11na' then
+	  configure_mtu(radio, site.wifi5)
+	end
+end
+
+
+uci:save('network')
+uci:commit('network')
diff --git a/package/gluon-mesh-batman-adv-core/Makefile b/package/gluon-mesh-batman-adv-core/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..fc4bc2248cce10ae8fe11d3b5edb62c944cd9da5
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/Makefile
@@ -0,0 +1,37 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-mesh-batman-adv-core
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-mesh-batman-adv-core
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Support for batman-adv meshing (core)
+  DEPENDS:=+gluon-core +firewall +kmod-ipt-nathelper +libiwinfo-lua
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-mesh-batman-adv-core/install
+	$(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-mesh-batman-adv-core/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+
+$(eval $(call BuildPackage,gluon-mesh-batman-adv-core))
diff --git a/package/gluon-mesh-batman-adv-core/check_site.lua b/package/gluon-mesh-batman-adv-core/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..8226d5dd6ff7d80489b52b933b306983e1d1b5ec
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/check_site.lua
@@ -0,0 +1,13 @@
+need_string('regdom')
+
+for _, config in ipairs({'wifi24', 'wifi5'}) do
+   need_string(config .. '.ssid')
+   need_number(config .. '.channel')
+   need_string(config .. '.htmode')
+   need_string(config .. '.mesh_ssid')
+   need_string_match(config .. '.mesh_bssid', '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$')
+   need_number(config .. '.mesh_mcast_rate')
+   need_number(config .. '.mesh_vlan', false)
+end
+
+need_boolean('mesh_on_wan', false)
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/neighbours.d/batadv b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/neighbours.d/batadv
new file mode 100644
index 0000000000000000000000000000000000000000..f93a11f9c1479cefd7b1d174b6433e715447f6a1
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/neighbours.d/batadv
@@ -0,0 +1,41 @@
+local json = require 'luci.json'
+local util = require 'luci.util'
+local fs = require 'nixio.fs'
+
+local ifname_address_cache = {}
+
+function ifname2address(ifname)
+  local ifaddress
+  if ifname_address_cache[ifname] ~= nil then
+    ifaddress = ifname_address_cache[ifname]
+  else
+    ifaddress = util.trim(fs.readfile("/sys/class/net/" .. ifname .. "/address"))
+    ifname_address_cache[ifname] = ifaddress
+  end
+
+  return ifaddress
+end
+
+function batadv()
+  local interfaces = {}
+  local list = io.lines("/sys/kernel/debug/batman_adv/bat0/originators")
+  for line in list do
+    local mac1, lastseen, tq, mac2, ifname =
+      line:match("^([0-9a-f:]+) +(%d+%.%d+)s +%( *(%d+)%) +([0-9a-f:]+) +%[ *(.-)%]")
+
+    if mac1 ~= nil and mac1 == mac2 then
+      ifaddress = ifname2address(ifname)
+      if interfaces[ifaddress] == nil then
+        interfaces[ifaddress] = { neighbours = {} }
+      end
+
+      interfaces[ifaddress].neighbours[mac1] = { tq = tonumber(tq)
+                                               , lastseen = tonumber(lastseen)
+                                               }
+    end
+  end
+
+  return interfaces
+end
+
+return batadv()
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/neighbours.d/wifi b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/neighbours.d/wifi
new file mode 100644
index 0000000000000000000000000000000000000000..d3754222352de70ce439f2df8c0382f4756c774b
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/neighbours.d/wifi
@@ -0,0 +1,41 @@
+local json = require 'luci.json'
+local util = require 'luci.util'
+local fs = require 'nixio.fs'
+local iwinfo = require 'iwinfo'
+
+function neighbours(iface)
+  local stations = {}
+  for k, v in pairs(iface.iw.assoclist(iface.ifname)) do
+    stations[k:lower()] = { signal = v.signal
+                          , noise = v.noise
+                          , inactive = v.inactive
+                          }
+  end
+
+  return stations
+end
+
+function interfaces()
+  local interfaces = {}
+  for _, line in ipairs(util.split(util.exec('batctl if'))) do
+    ifname = line:match('^(.-): active')
+    if ifname ~= nil then
+      pcall(function()
+        local address = util.trim(fs.readfile('/sys/class/net/' .. ifname .. '/address'))
+        local wifitype = iwinfo.type(ifname)
+        if wifitype ~= nil then
+          interfaces[address] = { ifname = ifname, iw = iwinfo[wifitype] }
+        end
+      end)
+    end
+  end
+
+  return interfaces
+end
+
+local wifi = {}
+for address, iface in pairs(interfaces()) do
+  wifi[address] = { neighbours = neighbours(iface) }
+end
+
+return wifi
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/nodeinfo.d/network/addresses b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/nodeinfo.d/network/addresses
new file mode 100644
index 0000000000000000000000000000000000000000..d55f83404e91a97eb88d2ea73bad1cf9e7fc71d3
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/nodeinfo.d/network/addresses
@@ -0,0 +1,12 @@
+local ip = require 'luci.ip'
+
+local addresses = {}
+
+for line in io.lines('/proc/net/if_inet6') do
+  local matches = { line:match('^' .. string.rep('(%x%x%x%x)', 8) .. string.rep(' %x%x', 4) .. '%s+([^%s]+)$') }
+  if matches[9] == 'br-client' then
+    table.insert(addresses, ip.IPv6(string.format('%s:%s:%s:%s:%s:%s:%s:%s', unpack(matches))):string():lower())
+  end
+end
+
+return addresses
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/nodeinfo.d/network/mesh_interfaces b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/nodeinfo.d/network/mesh_interfaces
new file mode 100644
index 0000000000000000000000000000000000000000..1fef5e104becb01cd3669d99fdb7b31cbcf55545
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/nodeinfo.d/network/mesh_interfaces
@@ -0,0 +1,15 @@
+local list = util.exec('batctl if')
+
+local interfaces = {}
+for _, line in ipairs(util.split(list)) do
+  local ifname = line:match('^(.-):')
+  if ifname ~= nil then
+    pcall(
+      function()
+	table.insert(interfaces, util.trim(fs.readfile('/sys/class/net/' .. ifname .. '/address')))
+      end
+    )
+  end
+end
+
+return interfaces
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/nodeinfo.d/software/batman-adv/version b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/nodeinfo.d/software/batman-adv/version
new file mode 100644
index 0000000000000000000000000000000000000000..abb317e9dc1efa75ba982b013a1e3ff2f7254093
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/nodeinfo.d/software/batman-adv/version
@@ -0,0 +1 @@
+return util.trim(fs.readfile('/sys/module/batman_adv/version'))
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/statistics.d/clients b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/statistics.d/clients
new file mode 100644
index 0000000000000000000000000000000000000000..235865ed3f357cc345de205d49d86cdec6d4f117
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/statistics.d/clients
@@ -0,0 +1,20 @@
+local list = io.lines("/sys/kernel/debug/batman_adv/bat0/transtable_local")
+
+local count = 0
+local wifi = 0
+for line in list do
+  local mac, _, flags, lastseen = line:match("^ %* ([0-9a-f:]+) *(.- )%[(.-)%] +(%d+%.%d+)")
+  if mac then
+    if not flags:match('P') then
+      count = count + 1
+
+      if flags:match('W') then
+        wifi = wifi +1
+      end
+    end
+  end
+end
+
+return { total = count
+       , wifi  = wifi
+       }
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/statistics.d/gateway b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/statistics.d/gateway
new file mode 100644
index 0000000000000000000000000000000000000000..a1be9ac027db12d4f4e422e39b4edf3bd9583378
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/statistics.d/gateway
@@ -0,0 +1,5 @@
+local gateway = util.trim(util.exec("batctl -m bat0 gateways | awk '/^=>/ { print $2 }'"))
+
+if gateway ~= '' then
+	return gateway
+end
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/statistics.d/traffic b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/statistics.d/traffic
new file mode 100644
index 0000000000000000000000000000000000000000..01f6b4abc0795e79ba81050ebe88f51d0e277a93
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/announce/statistics.d/traffic
@@ -0,0 +1,14 @@
+local ethtool = require 'ethtool_stats'
+
+local fields = ethtool.interface_stats('bat0')
+
+local traffic = {}
+for _, class in ipairs({'rx', 'tx', 'forward', 'mgmt_rx', 'mgmt_tx'}) do
+	traffic[class] = {
+		bytes = fields[class .. '_bytes'],
+		packets = fields[class],
+	}
+end
+traffic['tx']['dropped'] = fields['tx_dropped']
+
+return traffic
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/300-gluon-mesh-batman-adv-core-wan b/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/300-gluon-mesh-batman-adv-core-wan
new file mode 100755
index 0000000000000000000000000000000000000000..f84a104d1753ea12dec6f2ff04f653a9b9219976
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/300-gluon-mesh-batman-adv-core-wan
@@ -0,0 +1,14 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+local util = require 'gluon.util'
+local uci = require('luci.model.uci').cursor()
+
+
+if sysconfig.wan_ifname:match('%.') then
+  -- fix up duplicate mac addresses (for mesh-on-WAN)
+  uci:set('network', 'wan', 'macaddr', util.generate_mac(1, 0))
+  uci:save('network')
+  uci:commit('network')
+end
+
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/310-gluon-mesh-batman-adv-core-mesh b/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/310-gluon-mesh-batman-adv-core-mesh
new file mode 100755
index 0000000000000000000000000000000000000000..c9638c4b4075cc915519b1631b2bc4f433530833
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/310-gluon-mesh-batman-adv-core-mesh
@@ -0,0 +1,97 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+local sysctl = require 'gluon.sysctl'
+local uci = require('luci.model.uci').cursor()
+
+
+uci:delete('batman-adv', 'bat0')
+uci:section('batman-adv', 'mesh', 'bat0',
+	    {
+		    orig_interval = 5000,
+		    gw_mode = 'client',
+		    multicast_mode = 0,
+	    }
+)
+uci:save('batman-adv')
+uci:commit('batman-adv')
+
+
+if not uci:get('network', 'client') then
+  local ifname
+
+  if sysconfig.lan_ifname then
+    ifname = sysconfig.lan_ifname .. ' bat0'
+  else
+    ifname = 'bat0'
+  end
+
+  uci:section('network', 'interface', 'client',
+	      {
+		ifname = ifname,
+		type = 'bridge',
+		proto = 'dhcpv6',
+		reqprefix = 'no',
+	      }
+  )
+end
+
+uci:set('network', 'client', 'igmp_snooping', 0)
+uci:set('network', 'client', 'macaddr', sysconfig.primary_mac)
+uci:set('network', 'client', 'peerdns', 1)
+
+uci:delete('network', 'bat0')
+uci:section('network', 'interface', 'bat0',
+	    {
+		    ifname = 'bat0',
+		    proto = 'none',
+		    macaddr = sysconfig.primary_mac,
+	    }
+)
+
+uci:save('network')
+uci:commit('network')
+
+
+uci:delete('firewall', 'client')
+uci:section('firewall', 'zone', 'client',
+	    {
+		    name = 'client',
+		    network = {'client'},
+		    input = 'ACCEPT',
+		    output = 'ACCEPT',
+		    forward = 'REJECT',
+	    }
+)
+
+uci:section('firewall', 'rule', 'client_dns',
+	  {
+		  name = 'client_dns',
+		  src = 'client',
+		  dest_port = '53',
+		  target = 'REJECT',
+	  }
+)
+
+uci:save('firewall')
+uci:commit('firewall')
+
+
+local dnsmasq = uci:get_first('dhcp', 'dnsmasq')
+uci:set('dhcp', dnsmasq, 'boguspriv', 0)
+uci:set('dhcp', dnsmasq, 'localise_queries', 0)
+uci:set('dhcp', dnsmasq, 'rebind_protection', 0)
+
+uci:delete('dhcp', 'client')
+uci:section('dhcp', 'dhcp', 'client',
+	    {
+		    interface = 'client',
+		    ignore = 1,
+	    }
+)
+
+uci:save('dhcp')
+uci:commit('dhcp')
+
+
+sysctl.set('net.ipv6.conf.br-client.forwarding', 0)
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/320-gluon-mesh-batman-adv-core-wireless b/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/320-gluon-mesh-batman-adv-core-wireless
new file mode 100755
index 0000000000000000000000000000000000000000..004483e9ab7c8317629b8639480b5456c4becd8e
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/320-gluon-mesh-batman-adv-core-wireless
@@ -0,0 +1,102 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local util = require 'gluon.util'
+
+local uci = require('luci.model.uci').cursor()
+
+
+local function configure_radio(radio, index, config)
+  uci:delete('wireless', radio, 'disabled')
+
+  uci:set('wireless', radio, 'channel', config.channel)
+  uci:set('wireless', radio, 'htmode', config.htmode)
+  uci:set('wireless', radio, 'country', site.regdom)
+
+  local client = 'client_' .. radio
+  local mesh = 'mesh_' .. radio
+
+  local client_ifname
+  local mesh_ifname
+  local radio_suffix = radio:match('^radio(%d+)$')
+  if radio_suffix then
+    client_ifname = 'client' .. radio_suffix
+    mesh_ifname = 'mesh' .. radio_suffix
+  end
+
+  uci:delete('wireless', client)
+  uci:section('wireless', 'wifi-iface', client,
+	      {
+		device = radio,
+		network = 'client',
+		mode = 'ap',
+		ssid = config.ssid,
+		macaddr = util.generate_mac(2, index),
+		ifname = client_ifname,
+	      }
+  )
+
+  uci:delete('network', mesh)
+  uci:delete('network', mesh .. '_vlan')
+
+  if config.mesh_vlan then
+	uci:section('network', 'interface', mesh,
+	      {
+		proto = 'none',
+	      }
+	)
+	uci:section('network', 'interface', mesh .. '_vlan',
+	      {
+		ifname = '@' .. mesh .. '.' .. config.mesh_vlan,
+		proto = 'batadv',
+		mesh = 'bat0',
+	      }
+	)
+  else
+	uci:section('network', 'interface', mesh,
+	      {
+		proto = 'batadv',
+		mesh = 'bat0',
+	      }
+	)
+  end
+
+  uci:delete('wireless', mesh)
+  uci:section('wireless', 'wifi-iface', mesh,
+	      {
+		device = radio,
+		network = mesh,
+		mode = 'adhoc',
+		ssid = config.mesh_ssid,
+		bssid = config.mesh_bssid,
+		macaddr = util.generate_mac(3, index),
+		mcast_rate = config.mesh_mcast_rate,
+		ifname = mesh_ifname,
+	      }
+  )
+end
+
+
+local radios = {}
+
+uci:foreach('wireless', 'wifi-device',
+	    function(s)
+	      table.insert(radios, s['.name'])
+	    end
+)
+
+for index, radio in ipairs(radios) do
+	local hwmode = uci:get('wireless', radio, 'hwmode')
+
+	if hwmode == '11g' or hwmode == '11ng' then
+	  configure_radio(radio, index, site.wifi24)
+	elseif hwmode == '11a' or hwmode == '11na' then
+	  configure_radio(radio, index, site.wifi5)
+	end
+end
+
+
+uci:save('wireless')
+uci:save('network')
+uci:commit('wireless')
+uci:commit('network')
diff --git a/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/330-gluon-mesh-batman-adv-core-mesh-on-wan b/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/330-gluon-mesh-batman-adv-core-mesh-on-wan
new file mode 100755
index 0000000000000000000000000000000000000000..d40c572961a1bcefdb6ce51b40d7efe3012969dd
--- /dev/null
+++ b/package/gluon-mesh-batman-adv-core/files/lib/gluon/upgrade/330-gluon-mesh-batman-adv-core-mesh-on-wan
@@ -0,0 +1,18 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+
+local c = uci.cursor()
+
+if not c:get('network', 'mesh_wan') then
+  c:section('network', 'interface', 'mesh_wan',
+            { ifname = 'br-wan'
+            , proto  = 'batadv'
+            , mesh   = 'bat0'
+            , auto   = site.mesh_on_wan and 1 or 0
+            })
+end
+
+c:save('network')
+c:commit('network')
diff --git a/package/gluon-mesh-vpn-fastd/Makefile b/package/gluon-mesh-vpn-fastd/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..1547985c3b2a1e8451034cb7de495f9e76f2c6bb
--- /dev/null
+++ b/package/gluon-mesh-vpn-fastd/Makefile
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-mesh-vpn-fastd
+PKG_VERSION:=3
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-mesh-vpn-fastd
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Support for connecting batman-adv meshes via fastd
+  DEPENDS:=+gluon-core +gluon-mesh-batman-adv +gluon-wan-dnsmasq +fastd +iptables-mod-extra
+endef
+
+define Package/gluon-mesh-vpn-fastd/description
+	Gluon community wifi mesh firmware framework: fastd support
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-mesh-vpn-fastd/install
+	$(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-mesh-vpn-fastd/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-mesh-vpn-fastd))
diff --git a/package/gluon-mesh-vpn-fastd/check_site.lua b/package/gluon-mesh-vpn-fastd/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..fc9d8221d959080457a3f0660abff94d81c6699a
--- /dev/null
+++ b/package/gluon-mesh-vpn-fastd/check_site.lua
@@ -0,0 +1,14 @@
+need_string_array 'fastd_mesh_vpn.methods'
+need_number 'fastd_mesh_vpn.mtu'
+need_number 'fastd_mesh_vpn.backbone.limit'
+
+
+local function check_peer(k, _)
+   local prefix = string.format('fastd_mesh_vpn.backbone.peers[%q].', k)
+
+   need_string(prefix .. 'key')
+   need_string_array(prefix .. 'remotes')
+end
+
+need_table('fastd_mesh_vpn.backbone.peers', check_peer)
+need_boolean('fastd_mesh_vpn.enabled', false)
diff --git a/package/gluon-mesh-vpn-fastd/files/lib/gluon/announce/nodeinfo.d/software/fastd b/package/gluon-mesh-vpn-fastd/files/lib/gluon/announce/nodeinfo.d/software/fastd
new file mode 100644
index 0000000000000000000000000000000000000000..bbfbda089e4075e65a88925e1aa8be11c9a28e40
--- /dev/null
+++ b/package/gluon-mesh-vpn-fastd/files/lib/gluon/announce/nodeinfo.d/software/fastd
@@ -0,0 +1,4 @@
+return {
+	enabled = uci:get_bool('fastd', 'mesh_vpn', 'enabled'),
+	version = util.exec('fastd -v'):match('^[^%s]+%s+([^\n]+)'),
+}
diff --git a/package/gluon-mesh-vpn-fastd/files/lib/gluon/announce/statistics.d/mesh_vpn b/package/gluon-mesh-vpn-fastd/files/lib/gluon/announce/statistics.d/mesh_vpn
new file mode 100644
index 0000000000000000000000000000000000000000..5e4e6a7aef1483d78fef3a6770eca9834665b240
--- /dev/null
+++ b/package/gluon-mesh-vpn-fastd/files/lib/gluon/announce/statistics.d/mesh_vpn
@@ -0,0 +1,32 @@
+local nixio = require('nixio')
+local ltn12 = require('luci.ltn12')
+local json = require('luci.json')
+local uci = require('luci.model.uci').cursor()
+
+local socket_path = uci:get('fastd', 'mesh_vpn', 'status_socket')
+local fastd_sock = nixio.socket('unix', 'stream')
+
+if not fastd_sock:connect(socket_path) then
+  return nil
+end
+
+decoder = json.Decoder()
+ltn12.pump.all(ltn12.source.file(fastd_sock), decoder:sink())
+
+local status = decoder:get()
+
+local output = {}
+
+for key, peer in pairs(status.peers) do
+  local name, valid = peer.name:gsub('^mesh_vpn_backbone_peer_', '')
+  if valid == 1 then
+    if peer.connection then
+      output[name] = {}
+      output[name].established = peer.connection.established/1000
+    else
+      output[name] = json.null
+    end
+  end
+end
+
+return output
diff --git a/package/gluon-mesh-vpn-fastd/files/lib/gluon/mesh-vpn-fastd/iptables.rules b/package/gluon-mesh-vpn-fastd/files/lib/gluon/mesh-vpn-fastd/iptables.rules
new file mode 100644
index 0000000000000000000000000000000000000000..c1a16ee12c735db41a137c2bf1fd4694d0929472
--- /dev/null
+++ b/package/gluon-mesh-vpn-fastd/files/lib/gluon/mesh-vpn-fastd/iptables.rules
@@ -0,0 +1,3 @@
+*nat
+-I OUTPUT -m owner --gid-owner gluon-fastd -o lo -d 127.0.0.1 -p udp --dport 53 -j DNAT --to-destination :54
+COMMIT
diff --git a/package/gluon-mesh-vpn-fastd/files/lib/gluon/upgrade/400-mesh-vpn-fastd b/package/gluon-mesh-vpn-fastd/files/lib/gluon/upgrade/400-mesh-vpn-fastd
new file mode 100755
index 0000000000000000000000000000000000000000..0585f5a7edca1269a8b67631b0f081228047bbfd
--- /dev/null
+++ b/package/gluon-mesh-vpn-fastd/files/lib/gluon/upgrade/400-mesh-vpn-fastd
@@ -0,0 +1,93 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local users = require 'gluon.users'
+local util = require 'gluon.util'
+
+local uci = require('luci.model.uci').cursor()
+
+
+-- The previously used user is removed, we need root privileges to use the packet_mark option
+users.remove_user('gluon-fastd')
+
+-- Group for iptables rule
+users.add_group('gluon-fastd', 800)
+
+
+local enabled = uci:get('fastd', 'mesh_vpn', 'enabled')
+if not enabled then
+  enabled = site.fastd_mesh_vpn.enabled and 1 or 0
+end
+
+
+uci:section('fastd', 'fastd', 'mesh_vpn',
+	  {
+		  enabled = enabled,
+		  group = 'gluon-fastd',
+		  syslog_level = 'verbose',
+		  interface = 'mesh-vpn',
+		  mode = 'tap',
+		  mtu = site.fastd_mesh_vpn.mtu,
+		  secure_handshakes = 1,
+		  method = site.fastd_mesh_vpn.methods,
+		  packet_mark = 1,
+		  status_socket = '/var/run/fastd.mesh_vpn.socket',
+	  }
+)
+uci:delete('fastd', 'mesh_vpn', 'user')
+
+uci:delete('fastd', 'mesh_vpn_backbone')
+uci:section('fastd', 'peer_group', 'mesh_vpn_backbone',
+	  {
+		  enabled = 1,
+		  net = 'mesh_vpn',
+		  peer_limit = site.fastd_mesh_vpn.backbone.limit,
+	  }
+)
+
+uci:delete_all('fastd', 'peer',
+	     function(peer)
+	       return peer.net == 'mesh_vpn' and peer.group == 'mesh_vpn_backbone'
+	     end
+)
+
+for name, config in pairs(site.fastd_mesh_vpn.backbone.peers) do
+	uci:section('fastd', 'peer', 'mesh_vpn_backbone_peer_' .. name,
+		  {
+			  enabled = 1,
+			  net = 'mesh_vpn',
+			  group = 'mesh_vpn_backbone',
+			  key = config.key,
+			  remote = config.remotes,
+		  }
+	)
+end
+
+uci:save('fastd')
+uci:commit('fastd')
+
+
+uci:section('network', 'interface', 'mesh_vpn',
+	  {
+		  ifname = 'mesh-vpn',
+		  proto = 'batadv',
+		  mesh = 'bat0',
+		  mesh_no_rebroadcast = 1,
+		  macaddr = util.generate_mac(4, 0),
+	  }
+)
+
+uci:save('network')
+uci:commit('network')
+
+
+uci:section('firewall', 'include', 'mesh_vpn_dns',
+	  {
+	    type = 'restore',
+	    path = '/lib/gluon/mesh-vpn-fastd/iptables.rules',
+	    family = 'ipv4',
+	  }
+)
+
+uci:save('firewall')
+uci:commit('firewall')
diff --git a/package/gluon-mesh-vpn-fastd/files/lib/gluon/upgrade/410-mesh-vpn-fastd-generate-secret b/package/gluon-mesh-vpn-fastd/files/lib/gluon/upgrade/410-mesh-vpn-fastd-generate-secret
new file mode 100755
index 0000000000000000000000000000000000000000..68a2fe6763ca3c140452a032fcbf6b7d02853118
--- /dev/null
+++ b/package/gluon-mesh-vpn-fastd/files/lib/gluon/upgrade/410-mesh-vpn-fastd-generate-secret
@@ -0,0 +1,13 @@
+#!/usr/bin/lua
+
+local uci = require 'luci.model.uci'
+
+local c = uci.cursor()
+
+local secret = c:get("fastd", "mesh_vpn", "secret")
+
+if not secret or not secret:match(("%x"):rep(64)) then
+  c:set("fastd", "mesh_vpn", "secret", "generate")
+  c:save("fastd")
+  c:commit("fastd")
+end
diff --git a/package/gluon-neighbour-info/Makefile b/package/gluon-neighbour-info/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..74aa6e8f0d752f08aff5a8364fbe5f5fa8a1fd8a
--- /dev/null
+++ b/package/gluon-neighbour-info/Makefile
@@ -0,0 +1,39 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-neighbour-info
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-neighbour-info
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=neighbour-info
+  DEPENDS:=
+endef
+
+define Package/gluon-neighbour-info/description
+	Gluon community wifi mesh firmware framework: neighbour-info
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+	$(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
+endef
+
+define Package/gluon-neighbour-info/install
+	$(INSTALL_DIR) $(1)/usr/bin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-neighbour-info $(1)/usr/bin/
+endef
+
+$(eval $(call BuildPackage,gluon-neighbour-info))
diff --git a/package/gluon-neighbour-info/src/Makefile b/package/gluon-neighbour-info/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..16a3e95f1ba4d088ecfe5663f3dc0e88c82d0fa8
--- /dev/null
+++ b/package/gluon-neighbour-info/src/Makefile
@@ -0,0 +1,6 @@
+all: gluon-neighbour-info
+
+gluon-neighbour-info: gluon-neighbour-info.c
+
+clean:
+	rm gluon-neighbour-info
diff --git a/package/gluon-neighbour-info/src/gluon-neighbour-info.c b/package/gluon-neighbour-info/src/gluon-neighbour-info.c
new file mode 100644
index 0000000000000000000000000000000000000000..d8536de6dd6c118c2b1dcc8cbf0cd7c256c7235c
--- /dev/null
+++ b/package/gluon-neighbour-info/src/gluon-neighbour-info.c
@@ -0,0 +1,191 @@
+/*
+   Copyright (c) 2014, Nils Schneider <nils@nilsschneider.net>
+   All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+   */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <time.h>
+
+void usage() {
+  puts("Usage: gluon-neighbour-info [-h] [-s] [-t <sec>] -d <dest> -p <port> -i <if0> -r <request>");
+  puts("  -p <int>         UDP port");
+  puts("  -d <ip6>         multicast group, e.g. ff02:0:0:0:0:0:2:1001");
+  puts("  -i <string>      interface, e.g. eth0 ");
+  puts("  -r <string>      request, e.g. nodeinfo");
+  puts("  -t <sec>         timeout in seconds (default: 3)");
+  puts("  -s               output as server-sent events");
+  puts("  -h               this help\n");
+}
+
+void getclock(struct timeval *tv) {
+  struct timespec ts;
+  clock_gettime(CLOCK_MONOTONIC, &ts);
+  tv->tv_sec = ts.tv_sec;
+  tv->tv_usec = ts.tv_nsec / 1000;
+}
+
+/* Assumes a and b are normalized */
+void tv_subtract (struct timeval *r, struct timeval *a, struct timeval *b) {
+  r->tv_usec = a->tv_usec - b->tv_usec;
+  r->tv_sec = a->tv_sec - b->tv_sec;
+
+  if (r->tv_usec < 0) {
+    r->tv_usec += 1000000;
+    r->tv_sec -= 1;
+  }
+}
+
+ssize_t recvtimeout(int socket, void *buffer, size_t length, int flags, struct timeval *timeout, struct timeval *offset) {
+  struct timeval now, delta;
+  ssize_t ret;
+
+  setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, timeout, sizeof(*timeout));
+  ret = recv(socket, buffer, length, flags);
+
+  getclock(&now);
+  tv_subtract(&delta, &now, offset);
+  tv_subtract(timeout, timeout, &delta);
+
+  return ret;
+}
+
+int request(const int sock, const struct sockaddr_in6 *client_addr, const char *request, bool sse, int timeout) {
+  ssize_t ret;
+  char buffer[8192];
+
+  ret = sendto(sock, request, strlen(request), 0, (struct sockaddr *)client_addr, sizeof(struct sockaddr_in6));
+
+  if (ret < 0) {
+    perror("Error in sendto()");
+    exit(EXIT_FAILURE);
+  }
+
+  struct timeval tv_timeout, tv_offset;
+  tv_timeout.tv_sec = timeout;
+  tv_timeout.tv_usec = 0;
+
+  getclock(&tv_offset);
+
+  while (1) {
+    ret = recvtimeout(sock, buffer, sizeof(buffer), 0, &tv_timeout, &tv_offset);
+
+    if (ret < 0)
+      break;
+
+    if (sse)
+      fputs("event: neighbour\ndata: ", stdout);
+
+    fwrite(buffer, sizeof(char), ret, stdout);
+
+    if (sse)
+      fputs("\n\n", stdout);
+    else
+      fputs("\n", stdout);
+
+    fflush(stdout);
+  }
+
+  return 0;
+}
+
+int main(int argc, char **argv) {
+  int sock;
+  struct sockaddr_in6 client_addr = {};
+  char *request_string = NULL;
+  struct in6_addr mgroup_addr;
+
+  sock = socket(PF_INET6, SOCK_DGRAM, 0);
+
+  if (sock < 0) {
+    perror("creating socket");
+    exit(EXIT_FAILURE);
+  }
+
+  client_addr.sin6_family = AF_INET6;
+  client_addr.sin6_addr = in6addr_any;
+
+  opterr = 0;
+
+  int port_set = 0;
+  int destination_set = 0;
+  int timeout = 3;
+  bool sse = false;
+
+  int c;
+  while ((c = getopt(argc, argv, "p:d:r:i:t:sh")) != -1)
+    switch (c) {
+      case 'p':
+        client_addr.sin6_port = htons(atoi(optarg));
+        break;
+      case 'd':
+        if (!inet_pton(AF_INET6, optarg, &client_addr.sin6_addr)) {
+          perror("Invalid IPv6 address. This message will probably confuse you");
+          exit(EXIT_FAILURE);
+        }
+        break;
+      case 'i':
+        client_addr.sin6_scope_id = if_nametoindex(optarg);
+        if (client_addr.sin6_scope_id == 0) {
+          perror("Can not use interface");
+          exit(EXIT_FAILURE);
+        }
+        break;
+      case 'r':
+        request_string = optarg;
+        break;
+      case 't':
+        timeout = atoi(optarg);
+        break;
+      case 's':
+        sse = true;
+        break;
+      case 'h':
+        usage();
+        exit(EXIT_SUCCESS);
+        break;
+      default:
+        fprintf(stderr, "Invalid parameter %c ignored.\n", c);
+    }
+
+  if (request_string == NULL)
+    error(EXIT_FAILURE, 0, "No request string supplied");
+
+  if (sse)
+    fputs("Content-Type: text/event-stream\n\n", stdout);
+
+  request(sock, &client_addr, request_string, sse, timeout);
+
+  if (sse)
+    fputs("event: eot\ndata: null\n\n", stdout);
+
+  return EXIT_SUCCESS;
+}
diff --git a/package/gluon-next-node/Makefile b/package/gluon-next-node/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..df27ecbe2cd04c28f97f485cb1db4a1b3a96aed9
--- /dev/null
+++ b/package/gluon-next-node/Makefile
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-next-node
+PKG_VERSION:=3
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-next-node
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Next-node anycast address
+  DEPENDS:=+gluon-core +gluon-ebtables +gluon-mesh-batman-adv +kmod-macvlan
+endef
+
+define Package/gluon-next-node/description
+	Gluon community wifi mesh firmware framework: next-node anycast address
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-next-node/install
+	$(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-next-node/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-next-node))
diff --git a/package/gluon-next-node/check_site.lua b/package/gluon-next-node/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..83889a8e2ab4b180100dc5f221b0f7d863e65254
--- /dev/null
+++ b/package/gluon-next-node/check_site.lua
@@ -0,0 +1,4 @@
+need_string_match('next_node.ip4', '^%d+.%d+.%d+.%d+$')
+need_string_match('next_node.ip6', '^[%x:]+$')
+
+need_string_match('next_node.mac', '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$')
diff --git a/package/gluon-next-node/files/lib/gluon/ebtables/250-next-node b/package/gluon-next-node/files/lib/gluon/ebtables/250-next-node
new file mode 100644
index 0000000000000000000000000000000000000000..0df7abcc395446ecda40312d6738305e49c7e175
--- /dev/null
+++ b/package/gluon-next-node/files/lib/gluon/ebtables/250-next-node
@@ -0,0 +1,20 @@
+local site = require 'gluon.site_config'
+local next_node = site.next_node
+
+rule('FORWARD --logical-in br-client -p ARP --arp-ip-src ' .. next_node.ip4 .. ' -j DROP')
+rule('FORWARD --logical-in br-client -p ARP --arp-ip-dst ' .. next_node.ip4 .. ' -j DROP')
+
+rule('FORWARD --logical-out br-client -o bat0 -d ' .. next_node.mac .. ' -j DROP')
+rule('OUTPUT --logical-out br-client -o bat0 -d ' .. next_node.mac .. ' -j DROP')
+rule('FORWARD --logical-out br-client -o bat0 -s ' .. next_node.mac .. ' -j DROP')
+rule('OUTPUT --logical-out br-client -o bat0 -s ' .. next_node.mac .. ' -j DROP')
+
+rule('FORWARD --logical-out br-client -o bat0 -p IPv4 --ip-destination ' .. next_node.ip4 .. ' -j DROP')
+rule('OUTPUT --logical-out br-client -o bat0 -p IPv4 --ip-destination ' .. next_node.ip4 .. ' -j DROP')
+rule('FORWARD --logical-out br-client -o bat0 -p IPv4 --ip-source ' .. next_node.ip4 .. ' -j DROP')
+rule('OUTPUT --logical-out br-client -o bat0 -p IPv4 --ip-source ' .. next_node.ip4 .. ' -j DROP')
+
+rule('FORWARD --logical-out br-client -o bat0 -p IPv6 --ip6-destination ' .. next_node.ip6 .. ' -j DROP')
+rule('OUTPUT --logical-out br-client -o bat0 -p IPv6 --ip6-destination ' .. next_node.ip6 .. ' -j DROP')
+rule('FORWARD --logical-out br-client -o bat0 -p IPv6 --ip6-source ' .. next_node.ip6 .. ' -j DROP')
+rule('OUTPUT --logical-out br-client -o bat0 -p IPv6 --ip6-source ' .. next_node.ip6 .. ' -j DROP')
diff --git a/package/gluon-next-node/files/lib/gluon/upgrade/400-next-node b/package/gluon-next-node/files/lib/gluon/upgrade/400-next-node
new file mode 100755
index 0000000000000000000000000000000000000000..0ff959fe730ce6b17fed3468c557a2ee2a41267a
--- /dev/null
+++ b/package/gluon-next-node/files/lib/gluon/upgrade/400-next-node
@@ -0,0 +1,55 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+local ip = require 'luci.ip'
+
+local c = uci.cursor()
+
+
+c:delete('network', 'local_node_dev')
+c:section('network', 'device', 'local_node_dev',
+	  {
+		  name = 'local-node',
+		  ifname = 'br-client',
+		  type = 'macvlan',
+		  macaddr = site.next_node.mac,
+	  }
+)
+
+local prefix4 = ip.IPv4(site.prefix4)
+c:delete('network', 'local_node')
+c:section('network', 'interface', 'local_node',
+	  {
+		  ifname = 'local-node',
+		  proto = 'static',
+		  ipaddr = site.next_node.ip4,
+		  netmask = prefix4:mask():string(),
+		  ip6addr = site.next_node.ip6 .. '/128',
+	  }
+)
+
+c:delete('network', 'local_node_route6')
+c:section('network', 'route6', 'local_node_route6',
+	  {
+		  interface = 'client',
+		  target = site.prefix6,
+		  gateway = '::',
+	  }
+)
+
+c:save('network')
+c:commit('network')
+
+c:delete('firewall', 'local_node')
+c:section('firewall', 'zone', 'local_node',
+	  {
+		  name = 'local_node',
+		  network = {'local_node'},
+		  input = 'ACCEPT',
+		  output = 'ACCEPT',
+		  forward = 'REJECT',
+	  }
+)
+c:save('firewall')
+c:commit('firewall')
diff --git a/package/gluon-node-info/Makefile b/package/gluon-node-info/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0fe8a8dd624fcb265af5acff6e5715444ab1f3e2
--- /dev/null
+++ b/package/gluon-node-info/Makefile
@@ -0,0 +1,41 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-node-info
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-node-info
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Add /etc/config/gluon-node-info to uci
+  DEPENDS:=+gluon-core
+endef
+
+define Package/gluon-node-info/description
+	This packages creates /etc/config/gluon-node-info.
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-node-info/install
+	$(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-node-info/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-node-info))
diff --git a/package/gluon-node-info/check_site.lua b/package/gluon-node-info/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..d8d46408f0cf87f849d8a735211d0db85e4f1cf6
--- /dev/null
+++ b/package/gluon-node-info/check_site.lua
@@ -0,0 +1,8 @@
+local function check_role(k, _)
+   local role = string.format('roles.list[%q]', k)
+
+   need_string(role)
+end
+
+need_string('roles.default', false)
+need_table('roles.list', check_role, false)
diff --git a/package/gluon-node-info/files/etc/config/gluon-node-info b/package/gluon-node-info/files/etc/config/gluon-node-info
new file mode 100644
index 0000000000000000000000000000000000000000..8f6f472f426db2ffe90c0f3e63f190a583eb75ba
--- /dev/null
+++ b/package/gluon-node-info/files/etc/config/gluon-node-info
@@ -0,0 +1,6 @@
+config location
+	option share_location '0'
+
+config owner
+
+config system
diff --git a/package/gluon-node-info/files/lib/gluon/announce/nodeinfo.d/location b/package/gluon-node-info/files/lib/gluon/announce/nodeinfo.d/location
new file mode 100644
index 0000000000000000000000000000000000000000..72bf8878a1aa9d76abfbd9fd28fda9e47d61c7a4
--- /dev/null
+++ b/package/gluon-node-info/files/lib/gluon/announce/nodeinfo.d/location
@@ -0,0 +1,7 @@
+if uci:get_first('gluon-node-info', 'location', 'share_location', false) then
+	return {
+		latitude = tonumber(uci:get_first('gluon-node-info', 'location', 'latitude')),
+		longitude = tonumber(uci:get_first('gluon-node-info', 'location', 'longitude')),
+		altitude = tonumber(uci:get_first('gluon-node-info', 'location', 'altitude')),
+	}
+end
diff --git a/package/gluon-node-info/files/lib/gluon/announce/nodeinfo.d/owner b/package/gluon-node-info/files/lib/gluon/announce/nodeinfo.d/owner
new file mode 100644
index 0000000000000000000000000000000000000000..8a2a611d52544a423a2174260d7067711b160537
--- /dev/null
+++ b/package/gluon-node-info/files/lib/gluon/announce/nodeinfo.d/owner
@@ -0,0 +1,4 @@
+local contact = uci:get_first('gluon-node-info', 'owner', 'contact', '')
+if contact ~= '' then
+	return { contact = contact }
+end
diff --git a/package/gluon-node-info/files/lib/gluon/announce/nodeinfo.d/system/role b/package/gluon-node-info/files/lib/gluon/announce/nodeinfo.d/system/role
new file mode 100644
index 0000000000000000000000000000000000000000..38de47d796d132917eb0c734fc9a002ad95a39eb
--- /dev/null
+++ b/package/gluon-node-info/files/lib/gluon/announce/nodeinfo.d/system/role
@@ -0,0 +1,4 @@
+local role = uci:get_first('gluon-node-info', 'system', 'role', '')
+if role ~= '' then
+        return role
+end
diff --git a/package/gluon-node-info/files/lib/gluon/upgrade/500-node-info-system b/package/gluon-node-info/files/lib/gluon/upgrade/500-node-info-system
new file mode 100755
index 0000000000000000000000000000000000000000..a17b94617b45c78028720a35b50484b8de87340d
--- /dev/null
+++ b/package/gluon-node-info/files/lib/gluon/upgrade/500-node-info-system
@@ -0,0 +1,11 @@
+#!/usr/bin/lua
+
+local uci = require('luci.model.uci').cursor()
+
+local config = 'gluon-node-info'
+
+if not uci:get_first(config, 'system') then
+  uci:section(config, 'system')
+  uci:save(config)
+  uci:commit(config)
+end
diff --git a/package/gluon-node-info/files/lib/gluon/upgrade/510-node-info-role b/package/gluon-node-info/files/lib/gluon/upgrade/510-node-info-role
new file mode 100755
index 0000000000000000000000000000000000000000..6e54a23463627dd173b6932e243aeaa94c7f64b8
--- /dev/null
+++ b/package/gluon-node-info/files/lib/gluon/upgrade/510-node-info-role
@@ -0,0 +1,19 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require('luci.model.uci').cursor()
+
+local config = 'gluon-node-info'
+local role = uci:get(config, uci:get_first(config, 'system'), 'role')
+
+if site.roles then
+  default_role = site.roles.default
+else
+  default_role = ''
+end
+
+if not role then
+  uci:set(config, uci:get_first(config, 'system'), 'role', default_role)
+  uci:save(config)
+  uci:commit(config)
+end
diff --git a/package/gluon-radvd/Makefile b/package/gluon-radvd/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4736af60dff713989fac462a9960cd1ecfab86e1
--- /dev/null
+++ b/package/gluon-radvd/Makefile
@@ -0,0 +1,39 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-radvd
+PKG_VERSION:=3
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-radvd
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Advertise an IPv6 prefix from the node
+  DEPENDS:=+gluon-core +gluon-ebtables +gluon-mesh-batman-adv +librt
+endef
+
+define Package/gluon-radvd/description
+	Gluon community wifi mesh firmware framework: Advertise an IPv6 prefix from the node
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+	$(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
+endef
+
+define Package/gluon-radvd/install
+	$(CP) ./files/* $(1)/
+	$(INSTALL_DIR) $(1)/usr/sbin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-radvd $(1)/usr/sbin/
+endef
+
+$(eval $(call BuildPackage,gluon-radvd))
diff --git a/package/gluon-radvd/files/etc/init.d/gluon-radvd b/package/gluon-radvd/files/etc/init.d/gluon-radvd
new file mode 100755
index 0000000000000000000000000000000000000000..14815e76283a63119fca579021af0b4d5306fe83
--- /dev/null
+++ b/package/gluon-radvd/files/etc/init.d/gluon-radvd
@@ -0,0 +1,15 @@
+#!/bin/sh /etc/rc.common
+
+START=50
+
+SERVICE_WRITE_PID=1
+SERVICE_DAEMONIZE=1
+
+
+start() {
+	service_start /usr/sbin/gluon-radvd -i br-client -p $(lua -e 'print(require("gluon.site_config").prefix6)')
+}
+
+stop() {
+	service_stop /usr/sbin/gluon-radvd
+}
diff --git a/package/gluon-radvd/files/lib/gluon/ebtables/300-radv-input-output b/package/gluon-radvd/files/lib/gluon/ebtables/300-radv-input-output
new file mode 100644
index 0000000000000000000000000000000000000000..377d11cdcad700afd880c0e13f14aa8537393e3e
--- /dev/null
+++ b/package/gluon-radvd/files/lib/gluon/ebtables/300-radv-input-output
@@ -0,0 +1,2 @@
+rule 'INPUT -p IPv6 --ip6-protocol ipv6-icmp --ip6-icmp-type router-solicitation -i bat0 -j DROP'
+rule 'OUTPUT -p IPv6 --ip6-protocol ipv6-icmp --ip6-icmp-type router-advertisement -o bat0 -j DROP'
diff --git a/package/gluon-radvd/files/lib/gluon/upgrade/500-radvd-remove-user b/package/gluon-radvd/files/lib/gluon/upgrade/500-radvd-remove-user
new file mode 100755
index 0000000000000000000000000000000000000000..036406af919bdcb7c8a07fe831977e5239392f46
--- /dev/null
+++ b/package/gluon-radvd/files/lib/gluon/upgrade/500-radvd-remove-user
@@ -0,0 +1,5 @@
+#!/usr/bin/lua
+
+local users = require 'gluon.users'
+
+users.remove_user('gluon-radvd')
diff --git a/package/gluon-radvd/src/Makefile b/package/gluon-radvd/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..f0bc9034d2d11849f6ef3db96f01f33a4208ac30
--- /dev/null
+++ b/package/gluon-radvd/src/Makefile
@@ -0,0 +1,4 @@
+all: gluon-radvd
+
+gluon-radvd: gluon-radvd.c
+	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -Wall -o $@ $^ $(LDLIBS) -lrt
diff --git a/package/gluon-radvd/src/gluon-radvd.c b/package/gluon-radvd/src/gluon-radvd.c
new file mode 100644
index 0000000000000000000000000000000000000000..bd57eabcf9468dd0e8311e13a94f385d89b01942
--- /dev/null
+++ b/package/gluon-radvd/src/gluon-radvd.c
@@ -0,0 +1,647 @@
+/*
+  Copyright (c) 2014, Matthias Schiffer <mschiffer@universe-factory.net>
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <error.h>
+#include <ifaddrs.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+
+
+#define MAX_PREFIXES 8
+
+/* These are in seconds */
+#define AdvValidLifetime 86400u
+#define AdvPreferredLifetime 14400u
+#define AdvDefaultLifetime 0u
+#define AdvCurHopLimit 64u
+
+#define MinRtrAdvInterval 200u
+#define MaxRtrAdvInterval 600u
+
+/* And these in milliseconds */
+#define MAX_RA_DELAY_TIME 500u
+#define MIN_DELAY_BETWEEN_RAS 3000u
+
+
+struct icmpv6_opt {
+	uint8_t type;
+	uint8_t length;
+	uint8_t data[6];
+};
+
+
+struct iface {
+	bool ok;
+	unsigned int ifindex;
+	struct in6_addr ifaddr;
+	uint8_t mac[6];
+};
+
+static struct global {
+	struct iface iface;
+
+	struct timespec time;
+	struct timespec next_advert;
+	struct timespec next_advert_earliest;
+
+	int icmp_sock;
+	int rtnl_sock;
+
+	const char *ifname;
+
+	size_t n_prefixes;
+	struct in6_addr prefixes[MAX_PREFIXES];
+} G = {
+	.rtnl_sock = -1,
+	.icmp_sock = -1,
+};
+
+
+static inline void exit_errno(const char *message) {
+	error(1, errno, "error: %s", message);
+}
+
+static inline void warn_errno(const char *message) {
+	error(0, errno, "warning: %s", message);
+}
+
+
+static inline void update_time(void) {
+	clock_gettime(CLOCK_MONOTONIC, &G.time);
+}
+
+/* Compares two timespecs and returns true if tp1 is after tp2 */
+static inline bool timespec_after(const struct timespec *tp1, const struct timespec *tp2) {
+	return (tp1->tv_sec > tp2->tv_sec ||
+		(tp1->tv_sec == tp2->tv_sec && tp1->tv_nsec > tp2->tv_nsec));
+}
+
+/* Returns (tp1 - tp2) in milliseconds  */
+static inline int timespec_diff(const struct timespec *tp1, const struct timespec *tp2) {
+	return ((tp1->tv_sec - tp2->tv_sec))*1000 + (tp1->tv_nsec - tp2->tv_nsec)/1e6;
+}
+
+static inline void timespec_add(struct timespec *tp, unsigned int ms) {
+	tp->tv_sec += ms/1000;
+	tp->tv_nsec += (ms%1000) * 1e6;
+
+	if (tp->tv_nsec >= 1e9) {
+		tp->tv_nsec -= 1e9;
+		tp->tv_sec++;
+	}
+}
+
+
+static inline int setsockopt_int(int socket, int level, int option, int value) {
+	return setsockopt(socket, level, option, &value, sizeof(value));
+}
+
+
+static void init_random(void) {
+	unsigned int seed;
+	int fd = open("/dev/urandom", O_RDONLY);
+	if (fd < 0)
+		exit_errno("can't open /dev/urandom");
+
+	if (read(fd, &seed, sizeof(seed)) != sizeof(seed))
+		exit_errno("can't read from /dev/urandom");
+
+	close(fd);
+
+	srandom(seed);
+}
+
+static inline int rand_range(int min, int max) {
+	unsigned int r = (unsigned int)random();
+	return (r%(max-min) + min);
+}
+
+static void init_icmp(void) {
+	G.icmp_sock = socket(AF_INET6, SOCK_RAW|SOCK_NONBLOCK, IPPROTO_ICMPV6);
+	if (G.icmp_sock < 0)
+		exit_errno("can't open ICMP socket");
+
+	setsockopt_int(G.icmp_sock, IPPROTO_RAW, IPV6_CHECKSUM, 2);
+
+	setsockopt_int(G.icmp_sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 255);
+	setsockopt_int(G.icmp_sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, 1);
+
+	setsockopt_int(G.icmp_sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, 1);
+
+	struct icmp6_filter filter;
+	ICMP6_FILTER_SETBLOCKALL(&filter);
+	ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+	setsockopt(G.icmp_sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter));
+}
+
+static void init_rtnl(void) {
+	G.rtnl_sock = socket(AF_NETLINK, SOCK_DGRAM|SOCK_NONBLOCK, NETLINK_ROUTE);
+	if (G.rtnl_sock < 0)
+		exit_errno("can't open RTNL socket");
+
+	struct sockaddr_nl snl = {
+		.nl_family = AF_NETLINK,
+		.nl_groups = RTMGRP_LINK | RTMGRP_IPV6_IFADDR,
+	};
+	if (bind(G.rtnl_sock, (struct sockaddr *)&snl, sizeof(snl)) < 0)
+		exit_errno("can't bind RTNL socket");
+}
+
+
+static void schedule_advert(bool nodelay) {
+	struct timespec t = G.time;
+
+	if (nodelay)
+		timespec_add(&t, rand_range(0, MAX_RA_DELAY_TIME));
+	else
+		timespec_add(&t, rand_range(MinRtrAdvInterval*1000, MaxRtrAdvInterval*1000));
+
+	if (timespec_after(&G.next_advert_earliest, &t))
+		t = G.next_advert_earliest;
+
+	if (!nodelay || timespec_after(&G.next_advert, &t))
+		G.next_advert = t;
+}
+
+
+static int join_multicast(void) {
+	struct ipv6_mreq mreq = {
+		.ipv6mr_multiaddr = {
+			.s6_addr = {
+				/* all-routers address */
+				0xff, 0x02, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x02,
+			}
+		},
+		.ipv6mr_interface = G.iface.ifindex,
+	};
+
+	if (setsockopt(G.icmp_sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == 0) {
+		return 2;
+	}
+	else if (errno != EADDRINUSE) {
+		warn_errno("can't join multicast group");
+		return 0;
+	}
+
+	return 1;
+}
+
+static void update_interface(void) {
+	struct iface old;
+
+	memcpy(&old, &G.iface, sizeof(struct iface));
+	memset(&G.iface, 0, sizeof(struct iface));
+
+	/* Update ifindex */
+	G.iface.ifindex = if_nametoindex(G.ifname);
+	if (!G.iface.ifindex)
+		return;
+
+	/* Update MAC address */
+	struct ifreq ifr = {};
+	strncpy(ifr.ifr_name, G.ifname, sizeof(ifr.ifr_name)-1);
+	if (ioctl(G.icmp_sock, SIOCGIFHWADDR, &ifr) < 0)
+		return;
+
+	memcpy(G.iface.mac, ifr.ifr_hwaddr.sa_data, sizeof(G.iface.mac));
+
+	struct ifaddrs *addrs, *addr;
+	if (getifaddrs(&addrs) < 0) {
+		warn_errno("getifaddrs");
+		return;
+	}
+
+	memset(&G.iface.ifaddr, 0, sizeof(G.iface.ifaddr));
+
+	for (addr = addrs; addr; addr = addr->ifa_next) {
+		if (!addr->ifa_addr || addr->ifa_addr->sa_family != AF_INET6)
+			continue;
+
+		const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)addr->ifa_addr;
+		if (!IN6_IS_ADDR_LINKLOCAL(&in6->sin6_addr))
+			continue;
+
+		if (strncmp(addr->ifa_name, G.ifname, IFNAMSIZ-1) != 0)
+			continue;
+
+		G.iface.ifaddr = in6->sin6_addr;
+	}
+
+	freeifaddrs(addrs);
+
+	if (IN6_IS_ADDR_UNSPECIFIED(&G.iface.ifaddr))
+		return;
+
+	int joined = join_multicast();
+	if (!joined)
+		return;
+
+	setsockopt(G.icmp_sock, SOL_SOCKET, SO_BINDTODEVICE, G.ifname, strnlen(G.ifname, IFNAMSIZ-1));
+
+	G.iface.ok = true;
+
+	if (memcmp(&old, &G.iface, sizeof(struct iface)) != 0 || joined == 2)
+		schedule_advert(true);
+}
+
+
+static bool handle_rtnl_link(uint16_t type, const struct ifinfomsg *msg) {
+	switch (type) {
+	case RTM_NEWLINK:
+		if (!G.iface.ok)
+			return true;
+
+		break;
+
+	case RTM_SETLINK:
+		if ((unsigned)msg->ifi_index == G.iface.ifindex)
+			return true;
+
+		if (!G.iface.ok)
+			return true;
+
+		break;
+
+	case RTM_DELLINK:
+		if (G.iface.ok && (unsigned)msg->ifi_index == G.iface.ifindex)
+			return true;
+	}
+
+	return false;
+}
+
+static bool handle_rtnl_addr(uint16_t type, const struct ifaddrmsg *msg) {
+	switch (type) {
+	case RTM_NEWADDR:
+		if (!G.iface.ok && (unsigned)msg->ifa_index == G.iface.ifindex)
+			return true;
+
+		break;
+
+	case RTM_DELADDR:
+		if (G.iface.ok && (unsigned)msg->ifa_index == G.iface.ifindex)
+			return true;
+	}
+
+	return false;
+}
+
+static bool handle_rtnl_msg(uint16_t type, const void *data) {
+	switch (type) {
+	case RTM_NEWLINK:
+	case RTM_DELLINK:
+	case RTM_SETLINK:
+		return handle_rtnl_link(type, data);
+
+	case RTM_NEWADDR:
+	case RTM_DELADDR:
+		return handle_rtnl_addr(type, data);
+
+	default:
+		return false;
+	}
+}
+
+static void handle_rtnl(void) {
+	char buffer[4096];
+
+	ssize_t len = recv(G.rtnl_sock, buffer, sizeof(buffer), 0);
+	if (len < 0) {
+		warn_errno("recv");
+		return;
+	}
+
+	const struct nlmsghdr *nh;
+	for (nh = (struct nlmsghdr *)buffer; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
+		switch (nh->nlmsg_type) {
+		case NLMSG_DONE:
+			return;
+
+		case NLMSG_ERROR:
+			error(1, 0, "error: netlink error");
+
+		default:
+			if (handle_rtnl_msg(nh->nlmsg_type, NLMSG_DATA(nh))) {
+				update_interface();
+				return;
+			}
+		}
+	}
+}
+
+static void add_pktinfo(struct msghdr *msg) {
+	struct cmsghdr *cmsg = (struct cmsghdr*)((char*)msg->msg_control + msg->msg_controllen);
+
+	cmsg->cmsg_level = IPPROTO_IPV6;
+	cmsg->cmsg_type = IPV6_PKTINFO;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+
+	msg->msg_controllen += cmsg->cmsg_len;
+
+	struct in6_pktinfo pktinfo = {
+		.ipi6_addr = G.iface.ifaddr,
+		.ipi6_ifindex = G.iface.ifindex,
+	};
+
+	memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo));
+}
+
+
+static void handle_solicit(void) {
+	struct sockaddr_in6 addr;
+
+	uint8_t buffer[1500] __attribute__((aligned(8)));
+	struct iovec vec = { .iov_base = buffer, .iov_len = sizeof(buffer) };
+
+	uint8_t cbuf[1024] __attribute__((aligned(8)));
+
+
+	struct msghdr msg = {
+		.msg_name = &addr,
+		.msg_namelen = sizeof(addr),
+		.msg_iov = &vec,
+		.msg_iovlen = 1,
+		.msg_control = cbuf,
+		.msg_controllen = sizeof(cbuf),
+	};
+
+	ssize_t len = recvmsg(G.icmp_sock, &msg, 0);
+	if (len < (ssize_t)sizeof(struct nd_router_solicit)) {
+		if (len < 0)
+			warn_errno("recvmsg");
+
+		return;
+	}
+
+	struct cmsghdr *cmsg;
+	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+		if (cmsg->cmsg_level != IPPROTO_IPV6)
+			continue;
+
+		if (cmsg->cmsg_type != IPV6_HOPLIMIT)
+			continue;
+
+		if (*(int*)CMSG_DATA(cmsg) != 255)
+			return;
+
+		break;
+	}
+
+	const struct nd_router_solicit *s = (struct nd_router_solicit *)buffer;
+	if (s->nd_rs_hdr.icmp6_type != ND_ROUTER_SOLICIT || s->nd_rs_hdr.icmp6_code != 0)
+		return;
+
+	const struct icmpv6_opt *opt = (struct icmpv6_opt *)(buffer + sizeof(struct nd_router_solicit)), *end = (struct icmpv6_opt *)(buffer+len);
+
+	for (; opt < end; opt += opt->length) {
+		if (opt+1 < end)
+			return;
+
+		if (!opt->length)
+			return;
+
+		if (opt+opt->length < end)
+			return;
+
+		if (opt->type == ND_OPT_SOURCE_LINKADDR && IN6_IS_ADDR_UNSPECIFIED(&addr.sin6_addr))
+			return;
+	}
+
+	if (opt != end)
+		return;
+
+	schedule_advert(true);
+}
+
+static void send_advert(void) {
+	if (!G.iface.ok)
+		return;
+
+	struct nd_router_advert advert = {
+		.nd_ra_hdr = {
+			.icmp6_type = ND_ROUTER_ADVERT,
+			.icmp6_dataun.icmp6_un_data8 = {AdvCurHopLimit, 0 /* Flags */, (AdvDefaultLifetime>>8) & 0xff, AdvDefaultLifetime & 0xff },
+		},
+	};
+
+	struct icmpv6_opt lladdr = {ND_OPT_SOURCE_LINKADDR, 1, {}};
+	memcpy(lladdr.data, G.iface.mac, sizeof(G.iface.mac));
+
+	struct nd_opt_prefix_info prefixes[G.n_prefixes];
+
+	size_t i;
+	for (i = 0; i < G.n_prefixes; i++) {
+		prefixes[i] = (struct nd_opt_prefix_info){
+			.nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION,
+			.nd_opt_pi_len = 4,
+			.nd_opt_pi_prefix_len = 64,
+			.nd_opt_pi_flags_reserved = ND_OPT_PI_FLAG_AUTO|ND_OPT_PI_FLAG_ONLINK,
+			.nd_opt_pi_valid_time = htonl(AdvValidLifetime),
+			.nd_opt_pi_preferred_time = htonl(AdvPreferredLifetime),
+			.nd_opt_pi_prefix = G.prefixes[i],
+		};
+	}
+
+	struct iovec vec[3] = {
+		{ .iov_base = &advert, .iov_len = sizeof(advert) },
+		{ .iov_base = &lladdr, .iov_len = sizeof(lladdr) },
+		{ .iov_base = prefixes, .iov_len = sizeof(prefixes) },
+	};
+
+	struct sockaddr_in6 addr = {
+		.sin6_family = AF_INET6,
+		.sin6_addr = {
+			.s6_addr = {
+				/* all-nodes address */
+				0xff, 0x02, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x01,
+			}
+		},
+		.sin6_scope_id = G.iface.ifindex,
+	};
+
+	uint8_t cbuf[1024] __attribute__((aligned(8))) = {};
+
+	struct msghdr msg = {
+		.msg_name = &addr,
+		.msg_namelen = sizeof(addr),
+		.msg_iov = vec,
+		.msg_iovlen = 3,
+		.msg_control = cbuf,
+		.msg_controllen = 0,
+		.msg_flags = 0,
+	};
+
+	add_pktinfo(&msg);
+
+	if (sendmsg(G.icmp_sock, &msg, 0) < 0) {
+		G.iface.ok = false;
+		return;
+	}
+
+	G.next_advert_earliest = G.time;
+	timespec_add(&G.next_advert_earliest, MIN_DELAY_BETWEEN_RAS);
+
+	schedule_advert(false);
+}
+
+
+static void usage(void) {
+	fprintf(stderr, "Usage: gluon-radvd [-h] -i <interface> -p <prefix> [ -p <prefix> ... ]\n");
+}
+
+static void add_prefix(const char *prefix) {
+	if (G.n_prefixes == MAX_PREFIXES)
+		error(1, 0, "maximum number of prefixes is %i.", MAX_PREFIXES);
+
+	const size_t len = strlen(prefix)+1;
+	char prefix2[len];
+	memcpy(prefix2, prefix, len);
+
+	char *slash = strchr(prefix2, '/');
+	if (slash) {
+		*slash = 0;
+		if (strcmp(slash+1, "64") != 0)
+			goto error;
+	}
+
+	if (inet_pton(AF_INET6, prefix2, &G.prefixes[G.n_prefixes]) != 1)
+		goto error;
+
+	static const uint8_t zero[8] = {};
+	if (memcmp(G.prefixes[G.n_prefixes].s6_addr + 8, zero, 8) != 0)
+		goto error;
+
+	G.n_prefixes++;
+	return;
+
+  error:
+	error(1, 0, "invalid prefix %s (only prefixes of length 64 are supported).", prefix);
+}
+
+static void parse_cmdline(int argc, char *argv[]) {
+	int c;
+	while ((c = getopt(argc, argv, "i:p:h")) != -1) {
+		switch(c) {
+		case 'i':
+			if (G.ifname)
+				error(1, 0, "multiple interfaces are not supported.");
+
+			G.ifname = optarg;
+
+			break;
+
+		case 'p':
+			add_prefix(optarg);
+			break;
+
+		case 'h':
+			usage();
+			exit(0);
+
+		default:
+			usage();
+			exit(1);
+		}
+	}
+}
+
+int main(int argc, char *argv[]) {
+	parse_cmdline(argc, argv);
+
+	if (!G.ifname || !G.n_prefixes)
+		error(1, 0, "interface and prefix arguments are required.");
+
+	init_random();
+	init_icmp();
+	init_rtnl();
+
+	update_time();
+	G.next_advert = G.next_advert_earliest = G.time;
+
+	update_interface();
+
+	while (true) {
+		struct pollfd fds[2] = {
+			{ .fd = G.icmp_sock, .events = POLLIN },
+			{ .fd = G.rtnl_sock, .events = POLLIN },
+		};
+
+		int timeout = -1;
+
+		if (G.iface.ok) {
+			timeout = timespec_diff(&G.next_advert, &G.time);
+
+			if (timeout < 0)
+				timeout = 0;
+		}
+
+		int ret = poll(fds, 2, timeout);
+		if (ret < 0)
+			exit_errno("poll");
+
+		update_time();
+
+		if (fds[0].revents & POLLIN)
+			handle_solicit();
+		if (fds[1].revents & POLLIN)
+			handle_rtnl();
+
+		if (timespec_after(&G.time, &G.next_advert))
+			send_advert();
+	}
+}
diff --git a/package/gluon-setup-mode/Makefile b/package/gluon-setup-mode/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..99799927e3a9a36dd75cbdc2f931ad706b86fbf3
--- /dev/null
+++ b/package/gluon-setup-mode/Makefile
@@ -0,0 +1,43 @@
+# Copyright (C) 2012 Nils Schneider <nils at nilsschneider.net>
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-setup-mode
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-setup-mode
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Setup mode
+  DEPENDS:=+gluon-core +uhttpd +dnsmasq
+endef
+
+define Package/gluon-setup-mode/description
+	Offline mode to perform basic setup in a secure manner.
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-setup-mode/install
+	$(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-setup-mode/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-setup-mode))
diff --git a/package/gluon-setup-mode/check_site.lua b/package/gluon-setup-mode/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..07baaad3f1d80334655af4e29efe8e37a5d6e983
--- /dev/null
+++ b/package/gluon-setup-mode/check_site.lua
@@ -0,0 +1,2 @@
+need_boolean('setup_mode.skip', false)
+
diff --git a/package/gluon-setup-mode/files/etc/config/gluon-setup-mode b/package/gluon-setup-mode/files/etc/config/gluon-setup-mode
new file mode 100644
index 0000000000000000000000000000000000000000..c652728f6c47452a89d745ccdd78ebe8a7abee5c
--- /dev/null
+++ b/package/gluon-setup-mode/files/etc/config/gluon-setup-mode
@@ -0,0 +1,3 @@
+config setup_mode
+       option enabled '0'
+       option configured '0'
diff --git a/package/gluon-setup-mode/files/etc/hotplug.d/button/50-gluon-setup-mode b/package/gluon-setup-mode/files/etc/hotplug.d/button/50-gluon-setup-mode
new file mode 100755
index 0000000000000000000000000000000000000000..9a1ccf2fd1fff9356956a8640ca5091caceb2724
--- /dev/null
+++ b/package/gluon-setup-mode/files/etc/hotplug.d/button/50-gluon-setup-mode
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+
+wait=3
+
+
+wait_setup_mode() {
+	sleep $wait
+	uci set 'gluon-setup-mode.@setup_mode[0].enabled=1'
+	uci commit gluon-setup-mode
+	reboot
+}
+
+
+if [ "$BUTTON" = wps -o "$BUTTON" = reset ]; then
+	case "$ACTION" in
+		pressed)
+			wait_setup_mode &
+			PID=$!
+			echo $PID > /tmp/.wait_setup_mode
+			;;
+		released)
+			if [ -r /tmp/.wait_setup_mode ]; then
+				kill $(cat /tmp/.wait_setup_mode)
+				rm /tmp/.wait_setup_mode
+			fi
+			;;
+	esac
+fi
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/ash-login b/package/gluon-setup-mode/files/lib/gluon/setup-mode/ash-login
new file mode 100755
index 0000000000000000000000000000000000000000..3349c4445d2681fd92e76b06b7c3ebe7486dc4c5
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/ash-login
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec /bin/ash --login
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K50dropbear b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K50dropbear
new file mode 120000
index 0000000000000000000000000000000000000000..066549b3aabb4255cab1d3ffd1db5a65c9a01a38
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K50dropbear
@@ -0,0 +1 @@
+/etc/init.d/dropbear
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K89log b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K89log
new file mode 120000
index 0000000000000000000000000000000000000000..1e0c5ac02e7d9c3a8be6e85002a2ab61767c0a09
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K89log
@@ -0,0 +1 @@
+/etc/init.d/log
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K90network b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K90network
new file mode 120000
index 0000000000000000000000000000000000000000..0a43e66b8f035ecc09b334ecf2299665203ee9e8
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K90network
@@ -0,0 +1 @@
+S20network
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K98boot b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K98boot
new file mode 120000
index 0000000000000000000000000000000000000000..64aea5e82fac99bff0ce8961ef3cbbb208e013e8
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K98boot
@@ -0,0 +1 @@
+/etc/init.d/boot
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K99umount b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K99umount
new file mode 120000
index 0000000000000000000000000000000000000000..b02f4892fa6433b27182dc135916ebf1e720d92e
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K99umount
@@ -0,0 +1 @@
+/etc/init.d/umount
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S00sysfixtime b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S00sysfixtime
new file mode 120000
index 0000000000000000000000000000000000000000..a4fb1d5bd88ca445c4c62882ffe50bb5ce367382
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S00sysfixtime
@@ -0,0 +1 @@
+/etc/init.d/sysfixtime
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10boot b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10boot
new file mode 120000
index 0000000000000000000000000000000000000000..64aea5e82fac99bff0ce8961ef3cbbb208e013e8
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10boot
@@ -0,0 +1 @@
+/etc/init.d/boot
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10system b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10system
new file mode 120000
index 0000000000000000000000000000000000000000..81e8836ff672df99397ffb23d0cf69884268430c
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10system
@@ -0,0 +1 @@
+/etc/init.d/system
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S11sysctl b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S11sysctl
new file mode 120000
index 0000000000000000000000000000000000000000..b4ac535e9157932ba0f60da9313d277a1c723b14
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S11sysctl
@@ -0,0 +1 @@
+/etc/init.d/sysctl
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S12log b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S12log
new file mode 120000
index 0000000000000000000000000000000000000000..1e0c5ac02e7d9c3a8be6e85002a2ab61767c0a09
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S12log
@@ -0,0 +1 @@
+/etc/init.d/log
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S13haveged b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S13haveged
new file mode 100755
index 0000000000000000000000000000000000000000..b4af677a51a190e43a81cfddb2c3a3fc9dfb9f73
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S13haveged
@@ -0,0 +1,5 @@
+#!/bin/sh /etc/rc.common
+
+if /etc/init.d/haveged enabled; then
+	. /etc/init.d/haveged
+fi
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S15gluon-setup-mode b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S15gluon-setup-mode
new file mode 100755
index 0000000000000000000000000000000000000000..df8a0e2be604c583d76eda0257cd119d4ebe587b
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S15gluon-setup-mode
@@ -0,0 +1,21 @@
+#!/bin/sh /etc/rc.common
+
+START=15
+
+
+boot() {
+	local enabled="$(uci -q get 'gluon-setup-mode.@setup_mode[0].enabled')"
+	local configured="$(uci -q get 'gluon-setup-mode.@setup_mode[0].configured')"
+
+	uci set 'gluon-setup-mode.@setup_mode[0].enabled=0'
+	uci commit gluon-setup-mode
+
+	if [ "$enabled" = 1 -o "$configured" != 1 ]; then
+		lua -e 'uci_state=require("luci.model.uci").cursor_state(); uci_state:section("gluon-setup-mode", "setup_mode", nil, { running = "1" }); uci_state:save("gluon-setup-mode")'
+	else
+		# This can happen after an upgrade from a version before the config file was called gluon-setup-mode
+		# We'll just reboot to return to the normal mode...
+		/etc/init.d/done boot
+		reboot
+	fi
+}
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S20network b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S20network
new file mode 100755
index 0000000000000000000000000000000000000000..8bc0ba4f51ccd0a9cc7588cf16c16a36c418c449
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S20network
@@ -0,0 +1,86 @@
+#!/bin/sh /etc/rc.common
+
+SETUP_MODE_ADDR=192.168.1.1
+SETUP_MODE_NETMASK=255.255.255.0
+
+START=20
+STOP=90
+
+USE_PROCD=1
+
+
+delete_interface() {
+        [ "$1" = 'loopback' ] || uci_remove network "$1"
+}
+
+prepare_config() {
+(
+	export UCI_CONFIG_DIR=/var/gluon/setup-mode/config
+
+	mkdir -p "$UCI_CONFIG_DIR"
+
+	cp /etc/config/network "$UCI_CONFIG_DIR"
+
+	config_load network
+	config_foreach delete_interface interface
+
+	uci_add network interface setup
+	uci_set network setup ifname "$(lua -e 'print(require("gluon.sysconfig").setup_ifname)')"
+	uci_set network setup type 'bridge'
+	uci_set network setup proto 'static'
+	uci_set network setup ipaddr "$SETUP_MODE_ADDR"
+	uci_set network setup netmask "$SETUP_MODE_NETMASK"
+
+	uci_commit network
+)
+}
+
+init_switch() {
+	setup_switch() { return 0; }
+
+	include /lib/network
+	setup_switch
+}
+
+start_service() {
+	prepare_config
+	init_switch
+
+	procd_open_instance
+	procd_set_param command /sbin/netifd -c /var/gluon/setup-mode/config
+	procd_set_param respawn
+	procd_set_param watch network.interface
+	[ -e /proc/sys/kernel/core_pattern ] && {
+		procd_set_param limits core="unlimited"
+		echo '/tmp/%e.%p.%s.%t.core' > /proc/sys/kernel/core_pattern
+	}
+	procd_close_instance
+}
+
+reload_service() {
+	init_switch
+	ubus call network reload
+	/sbin/wifi reload_legacy
+}
+
+stop_service() {
+	/sbin/wifi down
+}
+
+service_running() {
+	ubus -t 30 wait_for network.interface
+	/sbin/wifi reload_legacy
+}
+
+restart() {
+	ifdown -a
+	sleep 1
+	trap '' TERM
+	stop "$@"
+	start "$@"
+}
+
+shutdown() {
+	ifdown -a
+	stop
+}
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S50dropbear b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S50dropbear
new file mode 120000
index 0000000000000000000000000000000000000000..066549b3aabb4255cab1d3ffd1db5a65c9a01a38
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S50dropbear
@@ -0,0 +1 @@
+/etc/init.d/dropbear
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S50telnet b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S50telnet
new file mode 100755
index 0000000000000000000000000000000000000000..b524b2cec41f17ec686dfc26048975c17f4e2de1
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S50telnet
@@ -0,0 +1,12 @@
+#!/bin/sh /etc/rc.common
+
+START=50
+
+USE_PROCD=1
+PROG=/usr/sbin/telnetd
+
+start_service() {
+	procd_open_instance
+	procd_set_param command "$PROG" -F -l /lib/gluon/setup-mode/ash-login
+	procd_close_instance
+}
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S50uhttpd b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S50uhttpd
new file mode 100755
index 0000000000000000000000000000000000000000..53118704b2030f8183681889b6e2b66241e5bda7
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S50uhttpd
@@ -0,0 +1,14 @@
+#!/bin/sh /etc/rc.common
+
+START=50
+
+USE_PROCD=1
+
+UHTTPD_BIN="/usr/sbin/uhttpd"
+
+start_service() {
+	procd_open_instance
+	procd_set_param respawn
+	procd_set_param command "$UHTTPD_BIN" -f -h /lib/gluon/setup-mode/www -x /cgi-bin -A 1 -R -p 0.0.0.0:80
+	procd_close_instance
+}
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S60dnsmasq b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S60dnsmasq
new file mode 100755
index 0000000000000000000000000000000000000000..d275b4bf0b1425eb8913e6621297e1e2a425d1c0
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S60dnsmasq
@@ -0,0 +1,17 @@
+#!/bin/sh /etc/rc.common
+
+SETUP_MODE_DHCP_RANGE=192.168.1.2,192.168.1.254
+
+
+START=60
+
+USE_PROCD=1
+PROG=/usr/sbin/dnsmasq
+
+
+start_service() {
+	procd_open_instance
+	procd_set_param command $PROG -k -p 0 -F $SETUP_MODE_DHCP_RANGE -l /tmp/dhcp.leases -O option:router
+	procd_set_param respawn
+	procd_close_instance
+}
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S95done b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S95done
new file mode 120000
index 0000000000000000000000000000000000000000..c9f302775486b577a2f264f46b19e4e7acef691d
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S95done
@@ -0,0 +1 @@
+/etc/init.d/done
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S96led b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S96led
new file mode 100755
index 0000000000000000000000000000000000000000..b01ae599287b37edaee7f84a982dab7b84a70409
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S96led
@@ -0,0 +1,11 @@
+#!/bin/sh /etc/rc.common
+
+START=96
+
+start() {
+	/etc/init.d/led start
+
+	. /etc/diag.sh
+	get_status_led
+	status_led_set_timer 1000 300
+}
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/www/cgi-bin/luci b/package/gluon-setup-mode/files/lib/gluon/setup-mode/www/cgi-bin/luci
new file mode 100755
index 0000000000000000000000000000000000000000..c5c98473469eba6bd6b21879690026bea0758960
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/www/cgi-bin/luci
@@ -0,0 +1,5 @@
+#!/usr/bin/lua
+require "luci.cacheloader"
+require "luci.sgi.cgi"
+luci.dispatcher.indexcache = "/tmp/luci-indexcache"
+luci.sgi.cgi.run()
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/www/index.html b/package/gluon-setup-mode/files/lib/gluon/setup-mode/www/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..0a7238b556d9dd3b49b7f131e9382500ff243414
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/www/index.html
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="refresh" content="0; URL=/cgi-bin/luci" />
+</head>
+<body style="background-color: black">
+<a style="color: white; text-decoration: none" href="/cgi-bin/luci">LuCI - Lua Configuration Interface</a>
+</body>
+</html>
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/www/luci-static b/package/gluon-setup-mode/files/lib/gluon/setup-mode/www/luci-static
new file mode 120000
index 0000000000000000000000000000000000000000..aea80e059aaf8a810c877971fb78481d9482fd31
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/setup-mode/www/luci-static
@@ -0,0 +1 @@
+/www/luci-static
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/upgrade/300-setup-mode b/package/gluon-setup-mode/files/lib/gluon/upgrade/300-setup-mode
new file mode 100755
index 0000000000000000000000000000000000000000..ae59c7aaa23bdcb08d583ae8a2f337531951e51b
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/upgrade/300-setup-mode
@@ -0,0 +1,16 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+
+local c = uci.cursor()
+
+if site.setup_mode
+   and site.setup_mode.skip
+   and not c:get_first('gluon-setup-mode', 'setup_mode', 'configured', false) then
+     local name = c:get_first("gluon-setup-mode", "setup_mode")
+     c:set("gluon-setup-mode", name, "configured", 1)
+     c:save('gluon-setup-mode')
+     c:commit('gluon-setup-mode')
+end
+
diff --git a/package/gluon-setup-mode/files/lib/gluon/upgrade/310-setup-mode-migrate b/package/gluon-setup-mode/files/lib/gluon/upgrade/310-setup-mode-migrate
new file mode 100755
index 0000000000000000000000000000000000000000..9d3b9b0aacdb1d8d9bf37153caa87ec58818bd1f
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/upgrade/310-setup-mode-migrate
@@ -0,0 +1,18 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+
+local c = uci.cursor()
+
+
+local old = c:get_first('gluon-config-mode', 'wizard', 'configured')
+if old == '1' then
+	local setup_mode = c:get_first('gluon-setup-mode', 'setup_mode')
+	c:set('gluon-setup-mode', setup_mode, 'configured', '1')
+
+	c:save('gluon-setup-mode')
+	c:commit('gluon-setup-mode')
+end
+
+os.remove('/etc/config/gluon-config-mode')
diff --git a/package/gluon-setup-mode/files/lib/gluon/upgrade/320-setup-ifname b/package/gluon-setup-mode/files/lib/gluon/upgrade/320-setup-ifname
new file mode 100755
index 0000000000000000000000000000000000000000..3c4a8afe819bfb934b003ec30ce1a627276f5528
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/gluon/upgrade/320-setup-ifname
@@ -0,0 +1,18 @@
+#!/usr/bin/lua
+
+local platform = require 'gluon.platform'
+local sysconfig = require 'gluon.sysconfig'
+
+
+if sysconfig.setup_ifname then
+   os.exit(0)
+end
+
+if platform.match('ar71xx', 'generic', {'cpe510', 'nanostation-m', 'nanostation-m-xw', 'unifi-outdoor-plus'}) then
+   sysconfig.setup_ifname = sysconfig.config_ifname or sysconfig.wan_ifname or sysconfig.lan_ifname
+else
+   sysconfig.setup_ifname = sysconfig.config_ifname or sysconfig.lan_ifname or sysconfig.wan_ifname
+end
+
+-- Remove the old sysconfig setting
+sysconfig.config_ifname = nil
diff --git a/package/gluon-setup-mode/files/lib/preinit/90_setup_mode b/package/gluon-setup-mode/files/lib/preinit/90_setup_mode
new file mode 100644
index 0000000000000000000000000000000000000000..396b4f767d22a38554e5d501eeb693fde902641c
--- /dev/null
+++ b/package/gluon-setup-mode/files/lib/preinit/90_setup_mode
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+
+setup_mode_enable() {
+	local enabled="$(uci -q get 'gluon-setup-mode.@setup_mode[0].enabled')"
+	local configured="$(uci -q get 'gluon-setup-mode.@setup_mode[0].configured')"
+
+	if [ "$enabled" = 1 -o "$configured" != 1 ]; then
+		echo '/lib/gluon/setup-mode/rc.d' > /tmp/rc_d_path
+	fi
+}
+
+boot_hook_add preinit_main setup_mode_enable
diff --git a/package/gluon-simple-tc/Makefile b/package/gluon-simple-tc/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..b01b96413940eaf836425e360acb66431f7543f6
--- /dev/null
+++ b/package/gluon-simple-tc/Makefile
@@ -0,0 +1,47 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-simple-tc
+PKG_VERSION:=4
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-simple-tc
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Bandwidth limit support
+  DEPENDS:=+gluon-core +kmod-sched +libnl-tiny
+endef
+
+define Package/gluon-simple-tc/description
+	Gluon community wifi mesh firmware framework: tc support
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+	$(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Build/Configure
+endef
+
+
+TARGET_CFLAGS += -I$(STAGING_DIR)/usr/include/libnl-tiny
+
+define Build/Compile
+	CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
+endef
+
+define Package/gluon-simple-tc/install
+	$(CP) ./files/* $(1)/
+	$(INSTALL_DIR) $(1)/usr/sbin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-simple-tc $(1)/usr/sbin/
+endef
+
+define Package/gluon-simple-tc/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-simple-tc))
diff --git a/package/gluon-simple-tc/check_site.lua b/package/gluon-simple-tc/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..95d4fd81c2a92a3cf0a73d8e3a4eaaef13bcce05
--- /dev/null
+++ b/package/gluon-simple-tc/check_site.lua
@@ -0,0 +1,10 @@
+local function check_entry(k, _)
+   local prefix = string.format('simple_tc[%q].', k)
+
+   need_string(prefix .. 'ifname')
+   need_boolean(prefix .. 'enabled')
+   need_number(prefix .. 'limit_egress')
+   need_number(prefix .. 'limit_ingress')
+end
+
+need_table('simple_tc', check_entry)
diff --git a/package/gluon-simple-tc/files/etc/config/gluon-simple-tc b/package/gluon-simple-tc/files/etc/config/gluon-simple-tc
new file mode 100644
index 0000000000000000000000000000000000000000..c9b784c639e822d2468295174ddd40663938a83b
--- /dev/null
+++ b/package/gluon-simple-tc/files/etc/config/gluon-simple-tc
@@ -0,0 +1,7 @@
+# Example config
+
+config interface 'example'
+       option enabled '0'
+       option ifname 'eth0'
+       option limit_egress '1000' # 1000 Kbit/s
+       option limit_ingress '5000' # 5000 Kbit/s
diff --git a/package/gluon-simple-tc/files/etc/hotplug.d/net/50-gluon-simple-tc b/package/gluon-simple-tc/files/etc/hotplug.d/net/50-gluon-simple-tc
new file mode 100644
index 0000000000000000000000000000000000000000..8dd827819a9b2ed2897dfeffb7a5578e189c2dc7
--- /dev/null
+++ b/package/gluon-simple-tc/files/etc/hotplug.d/net/50-gluon-simple-tc
@@ -0,0 +1,26 @@
+[ "$ACTION" = 'add' ] || exit 0
+
+config_load gluon-simple-tc
+
+
+tc_interface() {
+	local iface="$1"
+
+	config_get ifname "$iface" ifname
+
+	[ "$INTERFACE" = "$ifname" ] || return
+
+	config_get_bool enabled "$iface" enabled 0
+
+	[ "$enabled" -eq 1 ] || return
+
+	config_get limit_ingress "$iface" limit_ingress
+	config_get limit_egress "$iface" limit_egress
+
+	[ "$limit_ingress" ] || limit_ingress=-
+	[ "$limit_egress" ] || limit_egress=-
+
+	gluon-simple-tc "$INTERFACE" "$limit_ingress" "$limit_egress"
+}
+
+config_foreach tc_interface 'interface'
diff --git a/package/gluon-simple-tc/files/etc/modules-boot.d/30-gluon-simple-tc b/package/gluon-simple-tc/files/etc/modules-boot.d/30-gluon-simple-tc
new file mode 120000
index 0000000000000000000000000000000000000000..44318d6ea823b4a9d4081929f2c12472dc368c48
--- /dev/null
+++ b/package/gluon-simple-tc/files/etc/modules-boot.d/30-gluon-simple-tc
@@ -0,0 +1 @@
+../modules.d/30-gluon-simple-tc
\ No newline at end of file
diff --git a/package/gluon-simple-tc/files/etc/modules.d/30-gluon-simple-tc b/package/gluon-simple-tc/files/etc/modules.d/30-gluon-simple-tc
new file mode 100644
index 0000000000000000000000000000000000000000..72b238c73425a492bee920448e1e4e076b62eef3
--- /dev/null
+++ b/package/gluon-simple-tc/files/etc/modules.d/30-gluon-simple-tc
@@ -0,0 +1,4 @@
+sch_ingress
+sch_tbf
+cls_basic
+act_police
diff --git a/package/gluon-simple-tc/files/lib/gluon/upgrade/300-simple-tc-site-defaults b/package/gluon-simple-tc/files/lib/gluon/upgrade/300-simple-tc-site-defaults
new file mode 100755
index 0000000000000000000000000000000000000000..037cb45fd982ea0471386ad3bd52bd90568f41f1
--- /dev/null
+++ b/package/gluon-simple-tc/files/lib/gluon/upgrade/300-simple-tc-site-defaults
@@ -0,0 +1,23 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+
+local c = uci.cursor()
+
+
+for name, config in pairs(site.simple_tc) do
+  if not c:get('gluon-simple-tc', name) then
+    c:section('gluon-simple-tc', 'interface', name,
+	      {
+		ifname = config.ifname,
+		enabled = config.enabled and 1 or 0,
+		limit_egress = config.limit_egress,
+		limit_ingress = config.limit_ingress,
+	      }
+    )
+  end
+end
+
+c:save('gluon-simple-tc')
+c:commit('gluon-simple-tc')
diff --git a/package/gluon-simple-tc/src/Makefile b/package/gluon-simple-tc/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..502c6232d408b10ff0c8fdf8b5d6d5a6a17a3dcd
--- /dev/null
+++ b/package/gluon-simple-tc/src/Makefile
@@ -0,0 +1,4 @@
+all: gluon-simple-tc
+
+gluon-simple-tc: gluon-simple-tc.c
+	$(CC) -Iinclude $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -Wall -o $@ $^ $(LDLIBS) -lnl-tiny
diff --git a/package/gluon-simple-tc/src/gluon-simple-tc.c b/package/gluon-simple-tc/src/gluon-simple-tc.c
new file mode 100644
index 0000000000000000000000000000000000000000..9e5bb2065cb3e160048498961581e1c2132626fd
--- /dev/null
+++ b/package/gluon-simple-tc/src/gluon-simple-tc.c
@@ -0,0 +1,292 @@
+/*
+  Copyright (c) 2014, Matthias Schiffer <mschiffer@universe-factory.net>
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <error.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <arpa/inet.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+
+#include <linux/if_ether.h>
+#include <linux/pkt_cls.h>
+#include <linux/pkt_sched.h>
+#include <linux/rtnetlink.h>
+
+
+#include <netlink/msg.h>
+#include <netlink/attr.h>
+#include <netlink/socket.h>
+
+
+static struct nl_cb *cb;
+static struct nl_sock *sock;
+static double ticks;
+
+static unsigned ifindex;
+
+static bool nlexpect;
+static int nlerror;
+
+
+static inline void exit_errno(const char *message) {
+	error(1, errno, "error: %s", message);
+}
+
+static inline void warn_errno(const char *message) {
+	error(0, errno, "warning: %s", message);
+}
+
+
+static void read_psched(void) {
+	uint32_t clock_res;
+	uint32_t t2us;
+	uint32_t us2t;
+
+	FILE *f = fopen("/proc/net/psched", "r");
+	if (!f || fscanf(f, "%08x %08x %08x", &t2us, &us2t, &clock_res) != 3)
+		exit_errno("error reading /proc/net/psched");
+	fclose(f);
+
+	/* compatibility hack from iproute... */
+	if (clock_res == 1000000000)
+		t2us = us2t;
+
+	ticks = (double)t2us / us2t * clock_res;
+}
+
+
+static struct nl_msg * prepare_tcmsg(int type, int flags, uint32_t parent, uint32_t handle, uint32_t info) {
+	struct nl_msg *msg = nlmsg_alloc_simple(type, flags);
+	if (!msg)
+		exit_errno("nlmsg_alloc_simple");
+
+	struct tcmsg tcmsg;
+	memset(&tcmsg, 0, sizeof(tcmsg));
+
+	tcmsg.tcm_family = AF_UNSPEC;
+	tcmsg.tcm_ifindex = ifindex;
+	tcmsg.tcm_parent = parent;
+	tcmsg.tcm_handle = handle;
+	tcmsg.tcm_info = info;
+
+	nlmsg_append(msg, &tcmsg, sizeof(tcmsg), NLMSG_ALIGNTO);
+
+	return msg;
+}
+
+
+static int error_handler(struct sockaddr_nl *nla __attribute__((unused)), struct nlmsgerr *nlerr, void *arg __attribute__((unused))) {
+	if (!nlexpect || (nlerr->error != -ENOENT && nlerr->error != -EINVAL))
+		nlerror = -nlerr->error;
+
+	return NL_STOP;
+}
+
+static bool do_send(struct nl_msg *msg, bool expect) {
+	nlerror = 0;
+	nlexpect = expect;
+
+	nl_send_auto_complete(sock, msg);
+	nlmsg_free(msg);
+	nl_wait_for_ack(sock);
+
+	if (nlerror) {
+		error(0, nlerror, "netlink");
+		errno = nlerror;
+		return false;
+	}
+
+	return true;
+}
+
+
+static inline unsigned get_xmittime(double rate, unsigned size) {
+	return ticks * (size/rate);
+}
+
+
+static void complete_rate(struct tc_ratespec *r, uint32_t rtab[256]) {
+	r->linklayer = TC_LINKLAYER_ETHERNET;
+	r->cell_align = -1;
+	r->cell_log = 3;
+
+	unsigned i;
+	for (i = 0; i < 256; i++)
+		rtab[i] = get_xmittime(r->rate, (i + 1) << 3);
+}
+
+
+static void do_ingress(double rate) {
+	if (!do_send(prepare_tcmsg(RTM_DELQDISC, 0, TC_H_INGRESS, 0xffff0000, 0), true))
+		return;
+
+	if (rate < 0)
+		return;
+
+
+	struct nl_msg *msg = prepare_tcmsg(RTM_NEWQDISC, NLM_F_CREATE | NLM_F_EXCL, TC_H_INGRESS, 0xffff0000, 0);
+	nla_put_string(msg, TCA_KIND, "ingress");
+
+	if (!do_send(msg, false))
+		return;
+
+
+	msg = prepare_tcmsg(RTM_NEWTFILTER, NLM_F_CREATE | NLM_F_EXCL, 0xffff0000, 0, TC_H_MAKE(0, htons(ETH_P_ALL)));
+
+	const unsigned buffer = 10240;
+
+	struct tc_police p;
+	memset(&p, 0, sizeof(p));
+
+	/* Range check has been done in main() */
+	p.rate.rate = rate;
+	p.burst = get_xmittime(p.rate.rate, buffer);
+	p.action = TC_POLICE_SHOT;
+
+	uint32_t rtab[256];
+	complete_rate(&p.rate, rtab);
+
+	nla_put_string(msg, TCA_KIND, "basic");
+
+	struct nlattr *opts = nla_nest_start(msg, TCA_OPTIONS);
+	struct nlattr *police = nla_nest_start(msg, TCA_BASIC_POLICE);
+
+	nla_put(msg, TCA_POLICE_TBF, sizeof(p), &p);
+	nla_put(msg, TCA_POLICE_RATE, sizeof(rtab), rtab);
+
+	nla_nest_end(msg, police);
+	nla_nest_end(msg, opts);
+
+	do_send(msg, false);
+}
+
+static void do_egress(double rate) {
+	if (!do_send(prepare_tcmsg(RTM_DELQDISC, 0, TC_H_ROOT, 0, 0), true))
+		return;
+
+	if (rate < 0)
+		return;
+
+
+	struct nl_msg *msg = prepare_tcmsg(RTM_NEWQDISC, NLM_F_CREATE | NLM_F_EXCL, TC_H_ROOT, 0, 0);
+	const unsigned buffer = 2048;
+
+	struct tc_tbf_qopt opt;
+	memset(&opt, 0, sizeof(opt));
+
+	/* Range check has been done in main() */
+	opt.rate.rate = rate;
+	opt.limit = 0.05*rate + buffer;
+	opt.buffer = get_xmittime(opt.rate.rate, buffer);
+
+	uint32_t rtab[256];
+	complete_rate(&opt.rate, rtab);
+
+	nla_put_string(msg, TCA_KIND, "tbf");
+
+	struct nlattr *opts = nla_nest_start(msg, TCA_OPTIONS);
+	nla_put(msg, TCA_TBF_PARMS, sizeof(opt), &opt);
+	nla_put(msg, TCA_TBF_BURST, sizeof(buffer), &buffer);
+	nla_put(msg, TCA_TBF_RTAB, sizeof(rtab), rtab);
+	nla_nest_end(msg, opts);
+
+	do_send(msg, false);
+}
+
+
+static inline void usage(void) {
+	fprintf(stderr, "Usage: gluon-simple-tc <interface> <ingress Kbit/s>|- <egress Kbit/s>|-\n");
+	exit(1);
+}
+
+static inline void maxrate(void) {
+	error(1, 0, "error: maximum allowed rate it about 2^25 Kbit/s");
+}
+
+
+int main(int argc, char *argv[]) {
+	if (argc != 4)
+		usage();
+
+	double ingress = -1, egress = -1;
+	char *end;
+
+	ifindex = if_nametoindex(argv[1]);
+	if (!ifindex)
+		error(1, 0, "invalid interface: %s", argv[1]);
+
+	if (strcmp(argv[2], "-") != 0) {
+		ingress = strtod(argv[2], &end);
+		if (*end || ingress < 0)
+			usage();
+
+		ingress *= 125;
+
+		if (ingress >= (1ull << 32))
+			maxrate();
+	}
+
+	if (strcmp(argv[3], "-") != 0) {
+		egress = strtod(argv[3], &end);
+		if (*end || egress < 0)
+			usage();
+
+		egress *= 125;
+
+		if (egress >= (1ull << 32))
+			maxrate();
+	}
+
+	read_psched();
+
+	cb = nl_cb_alloc(NL_CB_DEFAULT);
+	nl_cb_err(cb, NL_CB_CUSTOM, error_handler, NULL);
+
+	sock = nl_socket_alloc_cb(cb);
+	if (!sock)
+		exit_errno("nl_socket_alloc");
+
+	if (nl_connect(sock, NETLINK_ROUTE))
+		exit_errno("nl_connect");
+
+	do_ingress(ingress);
+	do_egress(egress);
+
+	nl_socket_free(sock);
+	nl_cb_put(cb);
+
+	return 0;
+}
diff --git a/package/gluon-simple-tc/src/include/linux/pkt_cls.h b/package/gluon-simple-tc/src/include/linux/pkt_cls.h
new file mode 100644
index 0000000000000000000000000000000000000000..25731dfb3fcc2f331cbba3c8f415c75614c1f46d
--- /dev/null
+++ b/package/gluon-simple-tc/src/include/linux/pkt_cls.h
@@ -0,0 +1,483 @@
+#ifndef __LINUX_PKT_CLS_H
+#define __LINUX_PKT_CLS_H
+
+#include <linux/types.h>
+#include <linux/pkt_sched.h>
+
+/* I think i could have done better macros ; for now this is stolen from
+ * some arch/mips code - jhs
+*/
+#define _TC_MAKE32(x) ((x))
+
+#define _TC_MAKEMASK1(n) (_TC_MAKE32(1) << _TC_MAKE32(n))
+#define _TC_MAKEMASK(v,n) (_TC_MAKE32((_TC_MAKE32(1)<<(v))-1) << _TC_MAKE32(n))
+#define _TC_MAKEVALUE(v,n) (_TC_MAKE32(v) << _TC_MAKE32(n))
+#define _TC_GETVALUE(v,n,m) ((_TC_MAKE32(v) & _TC_MAKE32(m)) >> _TC_MAKE32(n))
+
+/* verdict bit breakdown 
+ *
+bit 0: when set -> this packet has been munged already
+
+bit 1: when set -> It is ok to munge this packet
+
+bit 2,3,4,5: Reclassify counter - sort of reverse TTL - if exceeded
+assume loop
+
+bit 6,7: Where this packet was last seen 
+0: Above the transmit example at the socket level
+1: on the Ingress
+2: on the Egress
+
+bit 8: when set --> Request not to classify on ingress. 
+
+bits 9,10,11: redirect counter -  redirect TTL. Loop avoidance
+
+ *
+ * */
+
+#define TC_MUNGED          _TC_MAKEMASK1(0)
+#define SET_TC_MUNGED(v)   ( TC_MUNGED | (v & ~TC_MUNGED))
+#define CLR_TC_MUNGED(v)   ( v & ~TC_MUNGED)
+
+#define TC_OK2MUNGE        _TC_MAKEMASK1(1)
+#define SET_TC_OK2MUNGE(v)   ( TC_OK2MUNGE | (v & ~TC_OK2MUNGE))
+#define CLR_TC_OK2MUNGE(v)   ( v & ~TC_OK2MUNGE)
+
+#define S_TC_VERD          _TC_MAKE32(2)
+#define M_TC_VERD          _TC_MAKEMASK(4,S_TC_VERD)
+#define G_TC_VERD(x)       _TC_GETVALUE(x,S_TC_VERD,M_TC_VERD)
+#define V_TC_VERD(x)       _TC_MAKEVALUE(x,S_TC_VERD)
+#define SET_TC_VERD(v,n)   ((V_TC_VERD(n)) | (v & ~M_TC_VERD))
+
+#define S_TC_FROM          _TC_MAKE32(6)
+#define M_TC_FROM          _TC_MAKEMASK(2,S_TC_FROM)
+#define G_TC_FROM(x)       _TC_GETVALUE(x,S_TC_FROM,M_TC_FROM)
+#define V_TC_FROM(x)       _TC_MAKEVALUE(x,S_TC_FROM)
+#define SET_TC_FROM(v,n)   ((V_TC_FROM(n)) | (v & ~M_TC_FROM))
+#define AT_STACK	0x0
+#define AT_INGRESS	0x1
+#define AT_EGRESS	0x2
+
+#define TC_NCLS          _TC_MAKEMASK1(8)
+#define SET_TC_NCLS(v)   ( TC_NCLS | (v & ~TC_NCLS))
+#define CLR_TC_NCLS(v)   ( v & ~TC_NCLS)
+
+#define S_TC_RTTL          _TC_MAKE32(9)
+#define M_TC_RTTL          _TC_MAKEMASK(3,S_TC_RTTL)
+#define G_TC_RTTL(x)       _TC_GETVALUE(x,S_TC_RTTL,M_TC_RTTL)
+#define V_TC_RTTL(x)       _TC_MAKEVALUE(x,S_TC_RTTL)
+#define SET_TC_RTTL(v,n)   ((V_TC_RTTL(n)) | (v & ~M_TC_RTTL))
+
+#define S_TC_AT          _TC_MAKE32(12)
+#define M_TC_AT          _TC_MAKEMASK(2,S_TC_AT)
+#define G_TC_AT(x)       _TC_GETVALUE(x,S_TC_AT,M_TC_AT)
+#define V_TC_AT(x)       _TC_MAKEVALUE(x,S_TC_AT)
+#define SET_TC_AT(v,n)   ((V_TC_AT(n)) | (v & ~M_TC_AT))
+
+/* Action attributes */
+enum {
+	TCA_ACT_UNSPEC,
+	TCA_ACT_KIND,
+	TCA_ACT_OPTIONS,
+	TCA_ACT_INDEX,
+	TCA_ACT_STATS,
+	__TCA_ACT_MAX
+};
+
+#define TCA_ACT_MAX __TCA_ACT_MAX
+#define TCA_OLD_COMPAT (TCA_ACT_MAX+1)
+#define TCA_ACT_MAX_PRIO 32
+#define TCA_ACT_BIND	1
+#define TCA_ACT_NOBIND	0
+#define TCA_ACT_UNBIND	1
+#define TCA_ACT_NOUNBIND	0
+#define TCA_ACT_REPLACE		1
+#define TCA_ACT_NOREPLACE	0
+#define MAX_REC_LOOP 4
+#define MAX_RED_LOOP 4
+
+#define TC_ACT_UNSPEC	(-1)
+#define TC_ACT_OK		0
+#define TC_ACT_RECLASSIFY	1
+#define TC_ACT_SHOT		2
+#define TC_ACT_PIPE		3
+#define TC_ACT_STOLEN		4
+#define TC_ACT_QUEUED		5
+#define TC_ACT_REPEAT		6
+#define TC_ACT_JUMP		0x10000000
+
+/* Action type identifiers*/
+enum {
+	TCA_ID_UNSPEC=0,
+	TCA_ID_POLICE=1,
+	/* other actions go here */
+	__TCA_ID_MAX=255
+};
+
+#define TCA_ID_MAX __TCA_ID_MAX
+
+struct tc_police {
+	__u32			index;
+	int			action;
+#define TC_POLICE_UNSPEC	TC_ACT_UNSPEC
+#define TC_POLICE_OK		TC_ACT_OK
+#define TC_POLICE_RECLASSIFY	TC_ACT_RECLASSIFY
+#define TC_POLICE_SHOT		TC_ACT_SHOT
+#define TC_POLICE_PIPE		TC_ACT_PIPE
+
+	__u32			limit;
+	__u32			burst;
+	__u32			mtu;
+	struct tc_ratespec	rate;
+	struct tc_ratespec	peakrate;
+	int 			refcnt;
+	int 			bindcnt;
+	__u32			capab;
+};
+
+struct tcf_t {
+	__u64   install;
+	__u64   lastuse;
+	__u64   expires;
+};
+
+struct tc_cnt {
+	int                   refcnt; 
+	int                   bindcnt;
+};
+
+#define tc_gen \
+	__u32                 index; \
+	__u32                 capab; \
+	int                   action; \
+	int                   refcnt; \
+	int                   bindcnt
+
+enum {
+	TCA_POLICE_UNSPEC,
+	TCA_POLICE_TBF,
+	TCA_POLICE_RATE,
+	TCA_POLICE_PEAKRATE,
+	TCA_POLICE_AVRATE,
+	TCA_POLICE_RESULT,
+	__TCA_POLICE_MAX
+#define TCA_POLICE_RESULT TCA_POLICE_RESULT
+};
+
+#define TCA_POLICE_MAX (__TCA_POLICE_MAX - 1)
+
+/* U32 filters */
+
+#define TC_U32_HTID(h) ((h)&0xFFF00000)
+#define TC_U32_USERHTID(h) (TC_U32_HTID(h)>>20)
+#define TC_U32_HASH(h) (((h)>>12)&0xFF)
+#define TC_U32_NODE(h) ((h)&0xFFF)
+#define TC_U32_KEY(h) ((h)&0xFFFFF)
+#define TC_U32_UNSPEC	0
+#define TC_U32_ROOT	(0xFFF00000)
+
+enum {
+	TCA_U32_UNSPEC,
+	TCA_U32_CLASSID,
+	TCA_U32_HASH,
+	TCA_U32_LINK,
+	TCA_U32_DIVISOR,
+	TCA_U32_SEL,
+	TCA_U32_POLICE,
+	TCA_U32_ACT,   
+	TCA_U32_INDEV,
+	TCA_U32_PCNT,
+	TCA_U32_MARK,
+	__TCA_U32_MAX
+};
+
+#define TCA_U32_MAX (__TCA_U32_MAX - 1)
+
+struct tc_u32_key {
+	__be32		mask;
+	__be32		val;
+	int		off;
+	int		offmask;
+};
+
+struct tc_u32_sel {
+	unsigned char		flags;
+	unsigned char		offshift;
+	unsigned char		nkeys;
+
+	__be16			offmask;
+	__u16			off;
+	short			offoff;
+
+	short			hoff;
+	__be32			hmask;
+	struct tc_u32_key	keys[0];
+};
+
+struct tc_u32_mark {
+	__u32		val;
+	__u32		mask;
+	__u32		success;
+};
+
+struct tc_u32_pcnt {
+	__u64 rcnt;
+	__u64 rhit;
+	__u64 kcnts[0];
+};
+
+/* Flags */
+
+#define TC_U32_TERMINAL		1
+#define TC_U32_OFFSET		2
+#define TC_U32_VAROFFSET	4
+#define TC_U32_EAT		8
+
+#define TC_U32_MAXDEPTH 8
+
+
+/* RSVP filter */
+
+enum {
+	TCA_RSVP_UNSPEC,
+	TCA_RSVP_CLASSID,
+	TCA_RSVP_DST,
+	TCA_RSVP_SRC,
+	TCA_RSVP_PINFO,
+	TCA_RSVP_POLICE,
+	TCA_RSVP_ACT,
+	__TCA_RSVP_MAX
+};
+
+#define TCA_RSVP_MAX (__TCA_RSVP_MAX - 1 )
+
+struct tc_rsvp_gpi {
+	__u32	key;
+	__u32	mask;
+	int	offset;
+};
+
+struct tc_rsvp_pinfo {
+	struct tc_rsvp_gpi dpi;
+	struct tc_rsvp_gpi spi;
+	__u8	protocol;
+	__u8	tunnelid;
+	__u8	tunnelhdr;
+	__u8	pad;
+};
+
+/* ROUTE filter */
+
+enum {
+	TCA_ROUTE4_UNSPEC,
+	TCA_ROUTE4_CLASSID,
+	TCA_ROUTE4_TO,
+	TCA_ROUTE4_FROM,
+	TCA_ROUTE4_IIF,
+	TCA_ROUTE4_POLICE,
+	TCA_ROUTE4_ACT,
+	__TCA_ROUTE4_MAX
+};
+
+#define TCA_ROUTE4_MAX (__TCA_ROUTE4_MAX - 1)
+
+
+/* FW filter */
+
+enum {
+	TCA_FW_UNSPEC,
+	TCA_FW_CLASSID,
+	TCA_FW_POLICE,
+	TCA_FW_INDEV, /*  used by CONFIG_NET_CLS_IND */
+	TCA_FW_ACT, /* used by CONFIG_NET_CLS_ACT */
+	TCA_FW_MASK,
+	__TCA_FW_MAX
+};
+
+#define TCA_FW_MAX (__TCA_FW_MAX - 1)
+
+/* TC index filter */
+
+enum {
+	TCA_TCINDEX_UNSPEC,
+	TCA_TCINDEX_HASH,
+	TCA_TCINDEX_MASK,
+	TCA_TCINDEX_SHIFT,
+	TCA_TCINDEX_FALL_THROUGH,
+	TCA_TCINDEX_CLASSID,
+	TCA_TCINDEX_POLICE,
+	TCA_TCINDEX_ACT,
+	__TCA_TCINDEX_MAX
+};
+
+#define TCA_TCINDEX_MAX     (__TCA_TCINDEX_MAX - 1)
+
+/* Flow filter */
+
+enum {
+	FLOW_KEY_SRC,
+	FLOW_KEY_DST,
+	FLOW_KEY_PROTO,
+	FLOW_KEY_PROTO_SRC,
+	FLOW_KEY_PROTO_DST,
+	FLOW_KEY_IIF,
+	FLOW_KEY_PRIORITY,
+	FLOW_KEY_MARK,
+	FLOW_KEY_NFCT,
+	FLOW_KEY_NFCT_SRC,
+	FLOW_KEY_NFCT_DST,
+	FLOW_KEY_NFCT_PROTO_SRC,
+	FLOW_KEY_NFCT_PROTO_DST,
+	FLOW_KEY_RTCLASSID,
+	FLOW_KEY_SKUID,
+	FLOW_KEY_SKGID,
+	FLOW_KEY_VLAN_TAG,
+	FLOW_KEY_RXHASH,
+	__FLOW_KEY_MAX,
+};
+
+#define FLOW_KEY_MAX	(__FLOW_KEY_MAX - 1)
+
+enum {
+	FLOW_MODE_MAP,
+	FLOW_MODE_HASH,
+};
+
+enum {
+	TCA_FLOW_UNSPEC,
+	TCA_FLOW_KEYS,
+	TCA_FLOW_MODE,
+	TCA_FLOW_BASECLASS,
+	TCA_FLOW_RSHIFT,
+	TCA_FLOW_ADDEND,
+	TCA_FLOW_MASK,
+	TCA_FLOW_XOR,
+	TCA_FLOW_DIVISOR,
+	TCA_FLOW_ACT,
+	TCA_FLOW_POLICE,
+	TCA_FLOW_EMATCHES,
+	TCA_FLOW_PERTURB,
+	__TCA_FLOW_MAX
+};
+
+#define TCA_FLOW_MAX	(__TCA_FLOW_MAX - 1)
+
+/* Basic filter */
+
+enum {
+	TCA_BASIC_UNSPEC,
+	TCA_BASIC_CLASSID,
+	TCA_BASIC_EMATCHES,
+	TCA_BASIC_ACT,
+	TCA_BASIC_POLICE,
+	__TCA_BASIC_MAX
+};
+
+#define TCA_BASIC_MAX (__TCA_BASIC_MAX - 1)
+
+
+/* Cgroup classifier */
+
+enum {
+	TCA_CGROUP_UNSPEC,
+	TCA_CGROUP_ACT,
+	TCA_CGROUP_POLICE,
+	TCA_CGROUP_EMATCHES,
+	__TCA_CGROUP_MAX,
+};
+
+#define TCA_CGROUP_MAX (__TCA_CGROUP_MAX - 1)
+
+/* BPF classifier */
+
+enum {
+	TCA_BPF_UNSPEC,
+	TCA_BPF_ACT,
+	TCA_BPF_POLICE,
+	TCA_BPF_CLASSID,
+	TCA_BPF_OPS_LEN,
+	TCA_BPF_OPS,
+	__TCA_BPF_MAX,
+};
+
+#define TCA_BPF_MAX (__TCA_BPF_MAX - 1)
+
+/* Extended Matches */
+
+struct tcf_ematch_tree_hdr {
+	__u16		nmatches;
+	__u16		progid;
+};
+
+enum {
+	TCA_EMATCH_TREE_UNSPEC,
+	TCA_EMATCH_TREE_HDR,
+	TCA_EMATCH_TREE_LIST,
+	__TCA_EMATCH_TREE_MAX
+};
+#define TCA_EMATCH_TREE_MAX (__TCA_EMATCH_TREE_MAX - 1)
+
+struct tcf_ematch_hdr {
+	__u16		matchid;
+	__u16		kind;
+	__u16		flags;
+	__u16		pad; /* currently unused */
+};
+
+/*  0                   1
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 
+ * +-----------------------+-+-+---+
+ * |         Unused        |S|I| R |
+ * +-----------------------+-+-+---+
+ *
+ * R(2) ::= relation to next ematch
+ *          where: 0 0 END (last ematch)
+ *                 0 1 AND
+ *                 1 0 OR
+ *                 1 1 Unused (invalid)
+ * I(1) ::= invert result
+ * S(1) ::= simple payload
+ */
+#define TCF_EM_REL_END	0
+#define TCF_EM_REL_AND	(1<<0)
+#define TCF_EM_REL_OR	(1<<1)
+#define TCF_EM_INVERT	(1<<2)
+#define TCF_EM_SIMPLE	(1<<3)
+
+#define TCF_EM_REL_MASK	3
+#define TCF_EM_REL_VALID(v) (((v) & TCF_EM_REL_MASK) != TCF_EM_REL_MASK)
+
+enum {
+	TCF_LAYER_LINK,
+	TCF_LAYER_NETWORK,
+	TCF_LAYER_TRANSPORT,
+	__TCF_LAYER_MAX
+};
+#define TCF_LAYER_MAX (__TCF_LAYER_MAX - 1)
+
+/* Ematch type assignments
+ *   1..32767		Reserved for ematches inside kernel tree
+ *   32768..65535	Free to use, not reliable
+ */
+#define	TCF_EM_CONTAINER	0
+#define	TCF_EM_CMP		1
+#define	TCF_EM_NBYTE		2
+#define	TCF_EM_U32		3
+#define	TCF_EM_META		4
+#define	TCF_EM_TEXT		5
+#define	TCF_EM_VLAN		6
+#define	TCF_EM_CANID		7
+#define	TCF_EM_IPSET		8
+#define	TCF_EM_MAX		8
+
+enum {
+	TCF_EM_PROG_TC
+};
+
+enum {
+	TCF_EM_OPND_EQ,
+	TCF_EM_OPND_GT,
+	TCF_EM_OPND_LT
+};
+
+#endif
diff --git a/package/gluon-simple-tc/src/include/linux/pkt_sched.h b/package/gluon-simple-tc/src/include/linux/pkt_sched.h
new file mode 100644
index 0000000000000000000000000000000000000000..d62316baae942c43b2558ed2768c88950516126c
--- /dev/null
+++ b/package/gluon-simple-tc/src/include/linux/pkt_sched.h
@@ -0,0 +1,846 @@
+#ifndef __LINUX_PKT_SCHED_H
+#define __LINUX_PKT_SCHED_H
+
+#include <linux/types.h>
+
+/* Logical priority bands not depending on specific packet scheduler.
+   Every scheduler will map them to real traffic classes, if it has
+   no more precise mechanism to classify packets.
+
+   These numbers have no special meaning, though their coincidence
+   with obsolete IPv6 values is not occasional :-). New IPv6 drafts
+   preferred full anarchy inspired by diffserv group.
+
+   Note: TC_PRIO_BESTEFFORT does not mean that it is the most unhappy
+   class, actually, as rule it will be handled with more care than
+   filler or even bulk.
+ */
+
+#define TC_PRIO_BESTEFFORT		0
+#define TC_PRIO_FILLER			1
+#define TC_PRIO_BULK			2
+#define TC_PRIO_INTERACTIVE_BULK	4
+#define TC_PRIO_INTERACTIVE		6
+#define TC_PRIO_CONTROL			7
+
+#define TC_PRIO_MAX			15
+
+/* Generic queue statistics, available for all the elements.
+   Particular schedulers may have also their private records.
+ */
+
+struct tc_stats {
+	__u64	bytes;			/* Number of enqueued bytes */
+	__u32	packets;		/* Number of enqueued packets	*/
+	__u32	drops;			/* Packets dropped because of lack of resources */
+	__u32	overlimits;		/* Number of throttle events when this
+					 * flow goes out of allocated bandwidth */
+	__u32	bps;			/* Current flow byte rate */
+	__u32	pps;			/* Current flow packet rate */
+	__u32	qlen;
+	__u32	backlog;
+};
+
+struct tc_estimator {
+	signed char	interval;
+	unsigned char	ewma_log;
+};
+
+/* "Handles"
+   ---------
+
+    All the traffic control objects have 32bit identifiers, or "handles".
+
+    They can be considered as opaque numbers from user API viewpoint,
+    but actually they always consist of two fields: major and
+    minor numbers, which are interpreted by kernel specially,
+    that may be used by applications, though not recommended.
+
+    F.e. qdisc handles always have minor number equal to zero,
+    classes (or flows) have major equal to parent qdisc major, and
+    minor uniquely identifying class inside qdisc.
+
+    Macros to manipulate handles:
+ */
+
+#define TC_H_MAJ_MASK (0xFFFF0000U)
+#define TC_H_MIN_MASK (0x0000FFFFU)
+#define TC_H_MAJ(h) ((h)&TC_H_MAJ_MASK)
+#define TC_H_MIN(h) ((h)&TC_H_MIN_MASK)
+#define TC_H_MAKE(maj,min) (((maj)&TC_H_MAJ_MASK)|((min)&TC_H_MIN_MASK))
+
+#define TC_H_UNSPEC	(0U)
+#define TC_H_ROOT	(0xFFFFFFFFU)
+#define TC_H_INGRESS    (0xFFFFFFF1U)
+
+/* Need to corrospond to iproute2 tc/tc_core.h "enum link_layer" */
+enum tc_link_layer {
+	TC_LINKLAYER_UNAWARE, /* Indicate unaware old iproute2 util */
+	TC_LINKLAYER_ETHERNET,
+	TC_LINKLAYER_ATM,
+};
+#define TC_LINKLAYER_MASK 0x0F /* limit use to lower 4 bits */
+
+struct tc_ratespec {
+	unsigned char	cell_log;
+	__u8		linklayer; /* lower 4 bits */
+	unsigned short	overhead;
+	short		cell_align;
+	unsigned short	mpu;
+	__u32		rate;
+};
+
+#define TC_RTAB_SIZE	1024
+
+struct tc_sizespec {
+	unsigned char	cell_log;
+	unsigned char	size_log;
+	short		cell_align;
+	int		overhead;
+	unsigned int	linklayer;
+	unsigned int	mpu;
+	unsigned int	mtu;
+	unsigned int	tsize;
+};
+
+enum {
+	TCA_STAB_UNSPEC,
+	TCA_STAB_BASE,
+	TCA_STAB_DATA,
+	__TCA_STAB_MAX
+};
+
+#define TCA_STAB_MAX (__TCA_STAB_MAX - 1)
+
+/* FIFO section */
+
+struct tc_fifo_qopt {
+	__u32	limit;	/* Queue length: bytes for bfifo, packets for pfifo */
+};
+
+/* PRIO section */
+
+#define TCQ_PRIO_BANDS	16
+#define TCQ_MIN_PRIO_BANDS 2
+
+struct tc_prio_qopt {
+	int	bands;			/* Number of bands */
+	__u8	priomap[TC_PRIO_MAX+1];	/* Map: logical priority -> PRIO band */
+};
+
+/* MULTIQ section */
+
+struct tc_multiq_qopt {
+	__u16	bands;			/* Number of bands */
+	__u16	max_bands;		/* Maximum number of queues */
+};
+
+/* PLUG section */
+
+#define TCQ_PLUG_BUFFER                0
+#define TCQ_PLUG_RELEASE_ONE           1
+#define TCQ_PLUG_RELEASE_INDEFINITE    2
+#define TCQ_PLUG_LIMIT                 3
+
+struct tc_plug_qopt {
+	/* TCQ_PLUG_BUFFER: Inset a plug into the queue and
+	 *  buffer any incoming packets
+	 * TCQ_PLUG_RELEASE_ONE: Dequeue packets from queue head
+	 *   to beginning of the next plug.
+	 * TCQ_PLUG_RELEASE_INDEFINITE: Dequeue all packets from queue.
+	 *   Stop buffering packets until the next TCQ_PLUG_BUFFER
+	 *   command is received (just act as a pass-thru queue).
+	 * TCQ_PLUG_LIMIT: Increase/decrease queue size
+	 */
+	int             action;
+	__u32           limit;
+};
+
+/* TBF section */
+
+struct tc_tbf_qopt {
+	struct tc_ratespec rate;
+	struct tc_ratespec peakrate;
+	__u32		limit;
+	__u32		buffer;
+	__u32		mtu;
+};
+
+enum {
+	TCA_TBF_UNSPEC,
+	TCA_TBF_PARMS,
+	TCA_TBF_RTAB,
+	TCA_TBF_PTAB,
+	TCA_TBF_RATE64,
+	TCA_TBF_PRATE64,
+	TCA_TBF_BURST,
+	TCA_TBF_PBURST,
+	__TCA_TBF_MAX,
+};
+
+#define TCA_TBF_MAX (__TCA_TBF_MAX - 1)
+
+
+/* TEQL section */
+
+/* TEQL does not require any parameters */
+
+/* SFQ section */
+
+struct tc_sfq_qopt {
+	unsigned	quantum;	/* Bytes per round allocated to flow */
+	int		perturb_period;	/* Period of hash perturbation */
+	__u32		limit;		/* Maximal packets in queue */
+	unsigned	divisor;	/* Hash divisor  */
+	unsigned	flows;		/* Maximal number of flows  */
+};
+
+struct tc_sfqred_stats {
+	__u32           prob_drop;      /* Early drops, below max threshold */
+	__u32           forced_drop;	/* Early drops, after max threshold */
+	__u32           prob_mark;      /* Marked packets, below max threshold */
+	__u32           forced_mark;    /* Marked packets, after max threshold */
+	__u32           prob_mark_head; /* Marked packets, below max threshold */
+	__u32           forced_mark_head;/* Marked packets, after max threshold */
+};
+
+struct tc_sfq_qopt_v1 {
+	struct tc_sfq_qopt v0;
+	unsigned int	depth;		/* max number of packets per flow */
+	unsigned int	headdrop;
+/* SFQRED parameters */
+	__u32		limit;		/* HARD maximal flow queue length (bytes) */
+	__u32		qth_min;	/* Min average length threshold (bytes) */
+	__u32		qth_max;	/* Max average length threshold (bytes) */
+	unsigned char   Wlog;		/* log(W)		*/
+	unsigned char   Plog;		/* log(P_max/(qth_max-qth_min))	*/
+	unsigned char   Scell_log;	/* cell size for idle damping */
+	unsigned char	flags;
+	__u32		max_P;		/* probability, high resolution */
+/* SFQRED stats */
+	struct tc_sfqred_stats stats;
+};
+
+
+struct tc_sfq_xstats {
+	__s32		allot;
+};
+
+/* RED section */
+
+enum {
+	TCA_RED_UNSPEC,
+	TCA_RED_PARMS,
+	TCA_RED_STAB,
+	TCA_RED_MAX_P,
+	__TCA_RED_MAX,
+};
+
+#define TCA_RED_MAX (__TCA_RED_MAX - 1)
+
+struct tc_red_qopt {
+	__u32		limit;		/* HARD maximal queue length (bytes)	*/
+	__u32		qth_min;	/* Min average length threshold (bytes) */
+	__u32		qth_max;	/* Max average length threshold (bytes) */
+	unsigned char   Wlog;		/* log(W)		*/
+	unsigned char   Plog;		/* log(P_max/(qth_max-qth_min))	*/
+	unsigned char   Scell_log;	/* cell size for idle damping */
+	unsigned char	flags;
+#define TC_RED_ECN		1
+#define TC_RED_HARDDROP		2
+#define TC_RED_ADAPTATIVE	4
+};
+
+struct tc_red_xstats {
+	__u32           early;          /* Early drops */
+	__u32           pdrop;          /* Drops due to queue limits */
+	__u32           other;          /* Drops due to drop() calls */
+	__u32           marked;         /* Marked packets */
+};
+
+/* GRED section */
+
+#define MAX_DPs 16
+
+enum {
+       TCA_GRED_UNSPEC,
+       TCA_GRED_PARMS,
+       TCA_GRED_STAB,
+       TCA_GRED_DPS,
+       TCA_GRED_MAX_P,
+	   __TCA_GRED_MAX,
+};
+
+#define TCA_GRED_MAX (__TCA_GRED_MAX - 1)
+
+struct tc_gred_qopt {
+	__u32		limit;        /* HARD maximal queue length (bytes)    */
+	__u32		qth_min;      /* Min average length threshold (bytes) */
+	__u32		qth_max;      /* Max average length threshold (bytes) */
+	__u32		DP;           /* up to 2^32 DPs */
+	__u32		backlog;
+	__u32		qave;
+	__u32		forced;
+	__u32		early;
+	__u32		other;
+	__u32		pdrop;
+	__u8		Wlog;         /* log(W)               */
+	__u8		Plog;         /* log(P_max/(qth_max-qth_min)) */
+	__u8		Scell_log;    /* cell size for idle damping */
+	__u8		prio;         /* prio of this VQ */
+	__u32		packets;
+	__u32		bytesin;
+};
+
+/* gred setup */
+struct tc_gred_sopt {
+	__u32		DPs;
+	__u32		def_DP;
+	__u8		grio;
+	__u8		flags;
+	__u16		pad1;
+};
+
+/* CHOKe section */
+
+enum {
+	TCA_CHOKE_UNSPEC,
+	TCA_CHOKE_PARMS,
+	TCA_CHOKE_STAB,
+	TCA_CHOKE_MAX_P,
+	__TCA_CHOKE_MAX,
+};
+
+#define TCA_CHOKE_MAX (__TCA_CHOKE_MAX - 1)
+
+struct tc_choke_qopt {
+	__u32		limit;		/* Hard queue length (packets)	*/
+	__u32		qth_min;	/* Min average threshold (packets) */
+	__u32		qth_max;	/* Max average threshold (packets) */
+	unsigned char   Wlog;		/* log(W)		*/
+	unsigned char   Plog;		/* log(P_max/(qth_max-qth_min))	*/
+	unsigned char   Scell_log;	/* cell size for idle damping */
+	unsigned char	flags;		/* see RED flags */
+};
+
+struct tc_choke_xstats {
+	__u32		early;          /* Early drops */
+	__u32		pdrop;          /* Drops due to queue limits */
+	__u32		other;          /* Drops due to drop() calls */
+	__u32		marked;         /* Marked packets */
+	__u32		matched;	/* Drops due to flow match */
+};
+
+/* HTB section */
+#define TC_HTB_NUMPRIO		8
+#define TC_HTB_MAXDEPTH		8
+#define TC_HTB_PROTOVER		3 /* the same as HTB and TC's major */
+
+struct tc_htb_opt {
+	struct tc_ratespec 	rate;
+	struct tc_ratespec 	ceil;
+	__u32	buffer;
+	__u32	cbuffer;
+	__u32	quantum;
+	__u32	level;		/* out only */
+	__u32	prio;
+};
+struct tc_htb_glob {
+	__u32 version;		/* to match HTB/TC */
+    	__u32 rate2quantum;	/* bps->quantum divisor */
+    	__u32 defcls;		/* default class number */
+	__u32 debug;		/* debug flags */
+
+	/* stats */
+	__u32 direct_pkts; /* count of non shaped packets */
+};
+enum {
+	TCA_HTB_UNSPEC,
+	TCA_HTB_PARMS,
+	TCA_HTB_INIT,
+	TCA_HTB_CTAB,
+	TCA_HTB_RTAB,
+	TCA_HTB_DIRECT_QLEN,
+	TCA_HTB_RATE64,
+	TCA_HTB_CEIL64,
+	__TCA_HTB_MAX,
+};
+
+#define TCA_HTB_MAX (__TCA_HTB_MAX - 1)
+
+struct tc_htb_xstats {
+	__u32 lends;
+	__u32 borrows;
+	__u32 giants;	/* too big packets (rate will not be accurate) */
+	__u32 tokens;
+	__u32 ctokens;
+};
+
+/* HFSC section */
+
+struct tc_hfsc_qopt {
+	__u16	defcls;		/* default class */
+};
+
+struct tc_service_curve {
+	__u32	m1;		/* slope of the first segment in bps */
+	__u32	d;		/* x-projection of the first segment in us */
+	__u32	m2;		/* slope of the second segment in bps */
+};
+
+struct tc_hfsc_stats {
+	__u64	work;		/* total work done */
+	__u64	rtwork;		/* work done by real-time criteria */
+	__u32	period;		/* current period */
+	__u32	level;		/* class level in hierarchy */
+};
+
+enum {
+	TCA_HFSC_UNSPEC,
+	TCA_HFSC_RSC,
+	TCA_HFSC_FSC,
+	TCA_HFSC_USC,
+	__TCA_HFSC_MAX,
+};
+
+#define TCA_HFSC_MAX (__TCA_HFSC_MAX - 1)
+
+
+/* CBQ section */
+
+#define TC_CBQ_MAXPRIO		8
+#define TC_CBQ_MAXLEVEL		8
+#define TC_CBQ_DEF_EWMA		5
+
+struct tc_cbq_lssopt {
+	unsigned char	change;
+	unsigned char	flags;
+#define TCF_CBQ_LSS_BOUNDED	1
+#define TCF_CBQ_LSS_ISOLATED	2
+	unsigned char  	ewma_log;
+	unsigned char  	level;
+#define TCF_CBQ_LSS_FLAGS	1
+#define TCF_CBQ_LSS_EWMA	2
+#define TCF_CBQ_LSS_MAXIDLE	4
+#define TCF_CBQ_LSS_MINIDLE	8
+#define TCF_CBQ_LSS_OFFTIME	0x10
+#define TCF_CBQ_LSS_AVPKT	0x20
+	__u32		maxidle;
+	__u32		minidle;
+	__u32		offtime;
+	__u32		avpkt;
+};
+
+struct tc_cbq_wrropt {
+	unsigned char	flags;
+	unsigned char	priority;
+	unsigned char	cpriority;
+	unsigned char	__reserved;
+	__u32		allot;
+	__u32		weight;
+};
+
+struct tc_cbq_ovl {
+	unsigned char	strategy;
+#define	TC_CBQ_OVL_CLASSIC	0
+#define	TC_CBQ_OVL_DELAY	1
+#define	TC_CBQ_OVL_LOWPRIO	2
+#define	TC_CBQ_OVL_DROP		3
+#define	TC_CBQ_OVL_RCLASSIC	4
+	unsigned char	priority2;
+	__u16		pad;
+	__u32		penalty;
+};
+
+struct tc_cbq_police {
+	unsigned char	police;
+	unsigned char	__res1;
+	unsigned short	__res2;
+};
+
+struct tc_cbq_fopt {
+	__u32		split;
+	__u32		defmap;
+	__u32		defchange;
+};
+
+struct tc_cbq_xstats {
+	__u32		borrows;
+	__u32		overactions;
+	__s32		avgidle;
+	__s32		undertime;
+};
+
+enum {
+	TCA_CBQ_UNSPEC,
+	TCA_CBQ_LSSOPT,
+	TCA_CBQ_WRROPT,
+	TCA_CBQ_FOPT,
+	TCA_CBQ_OVL_STRATEGY,
+	TCA_CBQ_RATE,
+	TCA_CBQ_RTAB,
+	TCA_CBQ_POLICE,
+	__TCA_CBQ_MAX,
+};
+
+#define TCA_CBQ_MAX	(__TCA_CBQ_MAX - 1)
+
+/* dsmark section */
+
+enum {
+	TCA_DSMARK_UNSPEC,
+	TCA_DSMARK_INDICES,
+	TCA_DSMARK_DEFAULT_INDEX,
+	TCA_DSMARK_SET_TC_INDEX,
+	TCA_DSMARK_MASK,
+	TCA_DSMARK_VALUE,
+	__TCA_DSMARK_MAX,
+};
+
+#define TCA_DSMARK_MAX (__TCA_DSMARK_MAX - 1)
+
+/* ATM  section */
+
+enum {
+	TCA_ATM_UNSPEC,
+	TCA_ATM_FD,		/* file/socket descriptor */
+	TCA_ATM_PTR,		/* pointer to descriptor - later */
+	TCA_ATM_HDR,		/* LL header */
+	TCA_ATM_EXCESS,		/* excess traffic class (0 for CLP)  */
+	TCA_ATM_ADDR,		/* PVC address (for output only) */
+	TCA_ATM_STATE,		/* VC state (ATM_VS_*; for output only) */
+	__TCA_ATM_MAX,
+};
+
+#define TCA_ATM_MAX	(__TCA_ATM_MAX - 1)
+
+/* Network emulator */
+
+enum {
+	TCA_NETEM_UNSPEC,
+	TCA_NETEM_CORR,
+	TCA_NETEM_DELAY_DIST,
+	TCA_NETEM_REORDER,
+	TCA_NETEM_CORRUPT,
+	TCA_NETEM_LOSS,
+	TCA_NETEM_RATE,
+	TCA_NETEM_ECN,
+	TCA_NETEM_RATE64,
+	__TCA_NETEM_MAX,
+};
+
+#define TCA_NETEM_MAX (__TCA_NETEM_MAX - 1)
+
+struct tc_netem_qopt {
+	__u32	latency;	/* added delay (us) */
+	__u32   limit;		/* fifo limit (packets) */
+	__u32	loss;		/* random packet loss (0=none ~0=100%) */
+	__u32	gap;		/* re-ordering gap (0 for none) */
+	__u32   duplicate;	/* random packet dup  (0=none ~0=100%) */
+	__u32	jitter;		/* random jitter in latency (us) */
+};
+
+struct tc_netem_corr {
+	__u32	delay_corr;	/* delay correlation */
+	__u32	loss_corr;	/* packet loss correlation */
+	__u32	dup_corr;	/* duplicate correlation  */
+};
+
+struct tc_netem_reorder {
+	__u32	probability;
+	__u32	correlation;
+};
+
+struct tc_netem_corrupt {
+	__u32	probability;
+	__u32	correlation;
+};
+
+struct tc_netem_rate {
+	__u32	rate;	/* byte/s */
+	__s32	packet_overhead;
+	__u32	cell_size;
+	__s32	cell_overhead;
+};
+
+enum {
+	NETEM_LOSS_UNSPEC,
+	NETEM_LOSS_GI,		/* General Intuitive - 4 state model */
+	NETEM_LOSS_GE,		/* Gilbert Elliot models */
+	__NETEM_LOSS_MAX
+};
+#define NETEM_LOSS_MAX (__NETEM_LOSS_MAX - 1)
+
+/* State transition probabilities for 4 state model */
+struct tc_netem_gimodel {
+	__u32	p13;
+	__u32	p31;
+	__u32	p32;
+	__u32	p14;
+	__u32	p23;
+};
+
+/* Gilbert-Elliot models */
+struct tc_netem_gemodel {
+	__u32 p;
+	__u32 r;
+	__u32 h;
+	__u32 k1;
+};
+
+#define NETEM_DIST_SCALE	8192
+#define NETEM_DIST_MAX		16384
+
+/* DRR */
+
+enum {
+	TCA_DRR_UNSPEC,
+	TCA_DRR_QUANTUM,
+	__TCA_DRR_MAX
+};
+
+#define TCA_DRR_MAX	(__TCA_DRR_MAX - 1)
+
+struct tc_drr_stats {
+	__u32	deficit;
+};
+
+/* MQPRIO */
+#define TC_QOPT_BITMASK 15
+#define TC_QOPT_MAX_QUEUE 16
+
+struct tc_mqprio_qopt {
+	__u8	num_tc;
+	__u8	prio_tc_map[TC_QOPT_BITMASK + 1];
+	__u8	hw;
+	__u16	count[TC_QOPT_MAX_QUEUE];
+	__u16	offset[TC_QOPT_MAX_QUEUE];
+};
+
+/* SFB */
+
+enum {
+	TCA_SFB_UNSPEC,
+	TCA_SFB_PARMS,
+	__TCA_SFB_MAX,
+};
+
+#define TCA_SFB_MAX (__TCA_SFB_MAX - 1)
+
+/*
+ * Note: increment, decrement are Q0.16 fixed-point values.
+ */
+struct tc_sfb_qopt {
+	__u32 rehash_interval;	/* delay between hash move, in ms */
+	__u32 warmup_time;	/* double buffering warmup time in ms (warmup_time < rehash_interval) */
+	__u32 max;		/* max len of qlen_min */
+	__u32 bin_size;		/* maximum queue length per bin */
+	__u32 increment;	/* probability increment, (d1 in Blue) */
+	__u32 decrement;	/* probability decrement, (d2 in Blue) */
+	__u32 limit;		/* max SFB queue length */
+	__u32 penalty_rate;	/* inelastic flows are rate limited to 'rate' pps */
+	__u32 penalty_burst;
+};
+
+struct tc_sfb_xstats {
+	__u32 earlydrop;
+	__u32 penaltydrop;
+	__u32 bucketdrop;
+	__u32 queuedrop;
+	__u32 childdrop; /* drops in child qdisc */
+	__u32 marked;
+	__u32 maxqlen;
+	__u32 maxprob;
+	__u32 avgprob;
+};
+
+#define SFB_MAX_PROB 0xFFFF
+
+/* QFQ */
+enum {
+	TCA_QFQ_UNSPEC,
+	TCA_QFQ_WEIGHT,
+	TCA_QFQ_LMAX,
+	__TCA_QFQ_MAX
+};
+
+#define TCA_QFQ_MAX	(__TCA_QFQ_MAX - 1)
+
+struct tc_qfq_stats {
+	__u32 weight;
+	__u32 lmax;
+};
+
+/* CODEL */
+
+enum {
+	TCA_CODEL_UNSPEC,
+	TCA_CODEL_TARGET,
+	TCA_CODEL_LIMIT,
+	TCA_CODEL_INTERVAL,
+	TCA_CODEL_ECN,
+	__TCA_CODEL_MAX
+};
+
+#define TCA_CODEL_MAX	(__TCA_CODEL_MAX - 1)
+
+struct tc_codel_xstats {
+	__u32	maxpacket; /* largest packet we've seen so far */
+	__u32	count;	   /* how many drops we've done since the last time we
+			    * entered dropping state
+			    */
+	__u32	lastcount; /* count at entry to dropping state */
+	__u32	ldelay;    /* in-queue delay seen by most recently dequeued packet */
+	__s32	drop_next; /* time to drop next packet */
+	__u32	drop_overlimit; /* number of time max qdisc packet limit was hit */
+	__u32	ecn_mark;  /* number of packets we ECN marked instead of dropped */
+	__u32	dropping;  /* are we in dropping state ? */
+};
+
+/* FQ_CODEL */
+
+enum {
+	TCA_FQ_CODEL_UNSPEC,
+	TCA_FQ_CODEL_TARGET,
+	TCA_FQ_CODEL_LIMIT,
+	TCA_FQ_CODEL_INTERVAL,
+	TCA_FQ_CODEL_ECN,
+	TCA_FQ_CODEL_FLOWS,
+	TCA_FQ_CODEL_QUANTUM,
+	__TCA_FQ_CODEL_MAX
+};
+
+#define TCA_FQ_CODEL_MAX	(__TCA_FQ_CODEL_MAX - 1)
+
+enum {
+	TCA_FQ_CODEL_XSTATS_QDISC,
+	TCA_FQ_CODEL_XSTATS_CLASS,
+};
+
+struct tc_fq_codel_qd_stats {
+	__u32	maxpacket;	/* largest packet we've seen so far */
+	__u32	drop_overlimit; /* number of time max qdisc
+				 * packet limit was hit
+				 */
+	__u32	ecn_mark;	/* number of packets we ECN marked
+				 * instead of being dropped
+				 */
+	__u32	new_flow_count; /* number of time packets
+				 * created a 'new flow'
+				 */
+	__u32	new_flows_len;	/* count of flows in new list */
+	__u32	old_flows_len;	/* count of flows in old list */
+};
+
+struct tc_fq_codel_cl_stats {
+	__s32	deficit;
+	__u32	ldelay;		/* in-queue delay seen by most recently
+				 * dequeued packet
+				 */
+	__u32	count;
+	__u32	lastcount;
+	__u32	dropping;
+	__s32	drop_next;
+};
+
+struct tc_fq_codel_xstats {
+	__u32	type;
+	union {
+		struct tc_fq_codel_qd_stats qdisc_stats;
+		struct tc_fq_codel_cl_stats class_stats;
+	};
+};
+
+/* FQ */
+
+enum {
+	TCA_FQ_UNSPEC,
+
+	TCA_FQ_PLIMIT,		/* limit of total number of packets in queue */
+
+	TCA_FQ_FLOW_PLIMIT,	/* limit of packets per flow */
+
+	TCA_FQ_QUANTUM,		/* RR quantum */
+
+	TCA_FQ_INITIAL_QUANTUM,		/* RR quantum for new flow */
+
+	TCA_FQ_RATE_ENABLE,	/* enable/disable rate limiting */
+
+	TCA_FQ_FLOW_DEFAULT_RATE,/* obsolete, do not use */
+
+	TCA_FQ_FLOW_MAX_RATE,	/* per flow max rate */
+
+	TCA_FQ_BUCKETS_LOG,	/* log2(number of buckets) */
+
+	TCA_FQ_FLOW_REFILL_DELAY,	/* flow credit refill delay in usec */
+
+	__TCA_FQ_MAX
+};
+
+#define TCA_FQ_MAX	(__TCA_FQ_MAX - 1)
+
+struct tc_fq_qd_stats {
+	__u64	gc_flows;
+	__u64	highprio_packets;
+	__u64	tcp_retrans;
+	__u64	throttled;
+	__u64	flows_plimit;
+	__u64	pkts_too_long;
+	__u64	allocation_errors;
+	__s64	time_next_delayed_flow;
+	__u32	flows;
+	__u32	inactive_flows;
+	__u32	throttled_flows;
+	__u32	pad;
+};
+
+/* Heavy-Hitter Filter */
+
+enum {
+	TCA_HHF_UNSPEC,
+	TCA_HHF_BACKLOG_LIMIT,
+	TCA_HHF_QUANTUM,
+	TCA_HHF_HH_FLOWS_LIMIT,
+	TCA_HHF_RESET_TIMEOUT,
+	TCA_HHF_ADMIT_BYTES,
+	TCA_HHF_EVICT_TIMEOUT,
+	TCA_HHF_NON_HH_WEIGHT,
+	__TCA_HHF_MAX
+};
+
+#define TCA_HHF_MAX	(__TCA_HHF_MAX - 1)
+
+struct tc_hhf_xstats {
+	__u32	drop_overlimit; /* number of times max qdisc packet limit
+				 * was hit
+				 */
+	__u32	hh_overlimit;   /* number of times max heavy-hitters was hit */
+	__u32	hh_tot_count;   /* number of captured heavy-hitters so far */
+	__u32	hh_cur_count;   /* number of current heavy-hitters */
+};
+
+/* PIE */
+enum {
+	TCA_PIE_UNSPEC,
+	TCA_PIE_TARGET,
+	TCA_PIE_LIMIT,
+	TCA_PIE_TUPDATE,
+	TCA_PIE_ALPHA,
+	TCA_PIE_BETA,
+	TCA_PIE_ECN,
+	TCA_PIE_BYTEMODE,
+	__TCA_PIE_MAX
+};
+#define TCA_PIE_MAX   (__TCA_PIE_MAX - 1)
+
+struct tc_pie_xstats {
+	__u32 prob;             /* current probability */
+	__u32 delay;            /* current delay in ms */
+	__u32 avg_dq_rate;      /* current average dq_rate in bits/pie_time */
+	__u32 packets_in;       /* total number of packets enqueued */
+	__u32 dropped;          /* packets dropped due to pie_action */
+	__u32 overlimit;        /* dropped due to lack of space in queue */
+	__u32 maxq;             /* maximum queue size */
+	__u32 ecn_mark;         /* packets marked with ecn*/
+};
+#endif
diff --git a/package/gluon-simple-tc/src/include/linux/rtnetlink.h b/package/gluon-simple-tc/src/include/linux/rtnetlink.h
new file mode 100644
index 0000000000000000000000000000000000000000..248fdd3f6ad4a6445a0936439360a2e2856aa30a
--- /dev/null
+++ b/package/gluon-simple-tc/src/include/linux/rtnetlink.h
@@ -0,0 +1,639 @@
+#ifndef __LINUX_RTNETLINK_H
+#define __LINUX_RTNETLINK_H
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <linux/if_link.h>
+#include <linux/if_addr.h>
+#include <linux/neighbour.h>
+
+/* rtnetlink families. Values up to 127 are reserved for real address
+ * families, values above 128 may be used arbitrarily.
+ */
+#define RTNL_FAMILY_IPMR		128
+#define RTNL_FAMILY_IP6MR		129
+#define RTNL_FAMILY_MAX			129
+
+/****
+ *		Routing/neighbour discovery messages.
+ ****/
+
+/* Types of messages */
+
+enum {
+	RTM_BASE	= 16,
+#define RTM_BASE	RTM_BASE
+
+	RTM_NEWLINK	= 16,
+#define RTM_NEWLINK	RTM_NEWLINK
+	RTM_DELLINK,
+#define RTM_DELLINK	RTM_DELLINK
+	RTM_GETLINK,
+#define RTM_GETLINK	RTM_GETLINK
+	RTM_SETLINK,
+#define RTM_SETLINK	RTM_SETLINK
+
+	RTM_NEWADDR	= 20,
+#define RTM_NEWADDR	RTM_NEWADDR
+	RTM_DELADDR,
+#define RTM_DELADDR	RTM_DELADDR
+	RTM_GETADDR,
+#define RTM_GETADDR	RTM_GETADDR
+
+	RTM_NEWROUTE	= 24,
+#define RTM_NEWROUTE	RTM_NEWROUTE
+	RTM_DELROUTE,
+#define RTM_DELROUTE	RTM_DELROUTE
+	RTM_GETROUTE,
+#define RTM_GETROUTE	RTM_GETROUTE
+
+	RTM_NEWNEIGH	= 28,
+#define RTM_NEWNEIGH	RTM_NEWNEIGH
+	RTM_DELNEIGH,
+#define RTM_DELNEIGH	RTM_DELNEIGH
+	RTM_GETNEIGH,
+#define RTM_GETNEIGH	RTM_GETNEIGH
+
+	RTM_NEWRULE	= 32,
+#define RTM_NEWRULE	RTM_NEWRULE
+	RTM_DELRULE,
+#define RTM_DELRULE	RTM_DELRULE
+	RTM_GETRULE,
+#define RTM_GETRULE	RTM_GETRULE
+
+	RTM_NEWQDISC	= 36,
+#define RTM_NEWQDISC	RTM_NEWQDISC
+	RTM_DELQDISC,
+#define RTM_DELQDISC	RTM_DELQDISC
+	RTM_GETQDISC,
+#define RTM_GETQDISC	RTM_GETQDISC
+
+	RTM_NEWTCLASS	= 40,
+#define RTM_NEWTCLASS	RTM_NEWTCLASS
+	RTM_DELTCLASS,
+#define RTM_DELTCLASS	RTM_DELTCLASS
+	RTM_GETTCLASS,
+#define RTM_GETTCLASS	RTM_GETTCLASS
+
+	RTM_NEWTFILTER	= 44,
+#define RTM_NEWTFILTER	RTM_NEWTFILTER
+	RTM_DELTFILTER,
+#define RTM_DELTFILTER	RTM_DELTFILTER
+	RTM_GETTFILTER,
+#define RTM_GETTFILTER	RTM_GETTFILTER
+
+	RTM_NEWACTION	= 48,
+#define RTM_NEWACTION   RTM_NEWACTION
+	RTM_DELACTION,
+#define RTM_DELACTION   RTM_DELACTION
+	RTM_GETACTION,
+#define RTM_GETACTION   RTM_GETACTION
+
+	RTM_NEWPREFIX	= 52,
+#define RTM_NEWPREFIX	RTM_NEWPREFIX
+
+	RTM_GETMULTICAST = 58,
+#define RTM_GETMULTICAST RTM_GETMULTICAST
+
+	RTM_GETANYCAST	= 62,
+#define RTM_GETANYCAST	RTM_GETANYCAST
+
+	RTM_NEWNEIGHTBL	= 64,
+#define RTM_NEWNEIGHTBL	RTM_NEWNEIGHTBL
+	RTM_GETNEIGHTBL	= 66,
+#define RTM_GETNEIGHTBL	RTM_GETNEIGHTBL
+	RTM_SETNEIGHTBL,
+#define RTM_SETNEIGHTBL	RTM_SETNEIGHTBL
+
+	RTM_NEWNDUSEROPT = 68,
+#define RTM_NEWNDUSEROPT RTM_NEWNDUSEROPT
+
+	RTM_NEWADDRLABEL = 72,
+#define RTM_NEWADDRLABEL RTM_NEWADDRLABEL
+	RTM_DELADDRLABEL,
+#define RTM_DELADDRLABEL RTM_DELADDRLABEL
+	RTM_GETADDRLABEL,
+#define RTM_GETADDRLABEL RTM_GETADDRLABEL
+
+	RTM_GETDCB = 78,
+#define RTM_GETDCB RTM_GETDCB
+	RTM_SETDCB,
+#define RTM_SETDCB RTM_SETDCB
+
+	RTM_NEWNETCONF = 80,
+#define RTM_NEWNETCONF RTM_NEWNETCONF
+	RTM_GETNETCONF = 82,
+#define RTM_GETNETCONF RTM_GETNETCONF
+
+	RTM_NEWMDB = 84,
+#define RTM_NEWMDB RTM_NEWMDB
+	RTM_DELMDB = 85,
+#define RTM_DELMDB RTM_DELMDB
+	RTM_GETMDB = 86,
+#define RTM_GETMDB RTM_GETMDB
+
+	__RTM_MAX,
+#define RTM_MAX		(((__RTM_MAX + 3) & ~3) - 1)
+};
+
+#define RTM_NR_MSGTYPES	(RTM_MAX + 1 - RTM_BASE)
+#define RTM_NR_FAMILIES	(RTM_NR_MSGTYPES >> 2)
+#define RTM_FAM(cmd)	(((cmd) - RTM_BASE) >> 2)
+
+/* 
+   Generic structure for encapsulation of optional route information.
+   It is reminiscent of sockaddr, but with sa_family replaced
+   with attribute type.
+ */
+
+struct rtattr {
+	unsigned short	rta_len;
+	unsigned short	rta_type;
+};
+
+/* Macros to handle rtattributes */
+
+#define RTA_ALIGNTO	4
+#define RTA_ALIGN(len) ( ((len)+RTA_ALIGNTO-1) & ~(RTA_ALIGNTO-1) )
+#define RTA_OK(rta,len) ((len) >= (int)sizeof(struct rtattr) && \
+			 (rta)->rta_len >= sizeof(struct rtattr) && \
+			 (rta)->rta_len <= (len))
+#define RTA_NEXT(rta,attrlen)	((attrlen) -= RTA_ALIGN((rta)->rta_len), \
+				 (struct rtattr*)(((char*)(rta)) + RTA_ALIGN((rta)->rta_len)))
+#define RTA_LENGTH(len)	(RTA_ALIGN(sizeof(struct rtattr)) + (len))
+#define RTA_SPACE(len)	RTA_ALIGN(RTA_LENGTH(len))
+#define RTA_DATA(rta)   ((void*)(((char*)(rta)) + RTA_LENGTH(0)))
+#define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0))
+
+
+
+
+/******************************************************************************
+ *		Definitions used in routing table administration.
+ ****/
+
+struct rtmsg {
+	unsigned char		rtm_family;
+	unsigned char		rtm_dst_len;
+	unsigned char		rtm_src_len;
+	unsigned char		rtm_tos;
+
+	unsigned char		rtm_table;	/* Routing table id */
+	unsigned char		rtm_protocol;	/* Routing protocol; see below	*/
+	unsigned char		rtm_scope;	/* See below */	
+	unsigned char		rtm_type;	/* See below	*/
+
+	unsigned		rtm_flags;
+};
+
+/* rtm_type */
+
+enum {
+	RTN_UNSPEC,
+	RTN_UNICAST,		/* Gateway or direct route	*/
+	RTN_LOCAL,		/* Accept locally		*/
+	RTN_BROADCAST,		/* Accept locally as broadcast,
+				   send as broadcast */
+	RTN_ANYCAST,		/* Accept locally as broadcast,
+				   but send as unicast */
+	RTN_MULTICAST,		/* Multicast route		*/
+	RTN_BLACKHOLE,		/* Drop				*/
+	RTN_UNREACHABLE,	/* Destination is unreachable   */
+	RTN_PROHIBIT,		/* Administratively prohibited	*/
+	RTN_THROW,		/* Not in this table		*/
+	RTN_NAT,		/* Translate this address	*/
+	RTN_XRESOLVE,		/* Use external resolver	*/
+	__RTN_MAX
+};
+
+#define RTN_MAX (__RTN_MAX - 1)
+
+
+/* rtm_protocol */
+
+#define RTPROT_UNSPEC	0
+#define RTPROT_REDIRECT	1	/* Route installed by ICMP redirects;
+				   not used by current IPv4 */
+#define RTPROT_KERNEL	2	/* Route installed by kernel		*/
+#define RTPROT_BOOT	3	/* Route installed during boot		*/
+#define RTPROT_STATIC	4	/* Route installed by administrator	*/
+
+/* Values of protocol >= RTPROT_STATIC are not interpreted by kernel;
+   they are just passed from user and back as is.
+   It will be used by hypothetical multiple routing daemons.
+   Note that protocol values should be standardized in order to
+   avoid conflicts.
+ */
+
+#define RTPROT_GATED	8	/* Apparently, GateD */
+#define RTPROT_RA	9	/* RDISC/ND router advertisements */
+#define RTPROT_MRT	10	/* Merit MRT */
+#define RTPROT_ZEBRA	11	/* Zebra */
+#define RTPROT_BIRD	12	/* BIRD */
+#define RTPROT_DNROUTED	13	/* DECnet routing daemon */
+#define RTPROT_XORP	14	/* XORP */
+#define RTPROT_NTK	15	/* Netsukuku */
+#define RTPROT_DHCP	16      /* DHCP client */
+#define RTPROT_MROUTED	17      /* Multicast daemon */
+
+/* rtm_scope
+
+   Really it is not scope, but sort of distance to the destination.
+   NOWHERE are reserved for not existing destinations, HOST is our
+   local addresses, LINK are destinations, located on directly attached
+   link and UNIVERSE is everywhere in the Universe.
+
+   Intermediate values are also possible f.e. interior routes
+   could be assigned a value between UNIVERSE and LINK.
+*/
+
+enum rt_scope_t {
+	RT_SCOPE_UNIVERSE=0,
+/* User defined values  */
+	RT_SCOPE_SITE=200,
+	RT_SCOPE_LINK=253,
+	RT_SCOPE_HOST=254,
+	RT_SCOPE_NOWHERE=255
+};
+
+/* rtm_flags */
+
+#define RTM_F_NOTIFY		0x100	/* Notify user of route change	*/
+#define RTM_F_CLONED		0x200	/* This route is cloned		*/
+#define RTM_F_EQUALIZE		0x400	/* Multipath equalizer: NI	*/
+#define RTM_F_PREFIX		0x800	/* Prefix addresses		*/
+
+/* Reserved table identifiers */
+
+enum rt_class_t {
+	RT_TABLE_UNSPEC=0,
+/* User defined values */
+	RT_TABLE_COMPAT=252,
+	RT_TABLE_DEFAULT=253,
+	RT_TABLE_MAIN=254,
+	RT_TABLE_LOCAL=255,
+	RT_TABLE_MAX=0xFFFFFFFF
+};
+
+
+/* Routing message attributes */
+
+enum rtattr_type_t {
+	RTA_UNSPEC,
+	RTA_DST,
+	RTA_SRC,
+	RTA_IIF,
+	RTA_OIF,
+	RTA_GATEWAY,
+	RTA_PRIORITY,
+	RTA_PREFSRC,
+	RTA_METRICS,
+	RTA_MULTIPATH,
+	RTA_PROTOINFO, /* no longer used */
+	RTA_FLOW,
+	RTA_CACHEINFO,
+	RTA_SESSION, /* no longer used */
+	RTA_MP_ALGO, /* no longer used */
+	RTA_TABLE,
+	RTA_MARK,
+	RTA_MFC_STATS,
+	__RTA_MAX
+};
+
+#define RTA_MAX (__RTA_MAX - 1)
+
+#define RTM_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))))
+#define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg))
+
+/* RTM_MULTIPATH --- array of struct rtnexthop.
+ *
+ * "struct rtnexthop" describes all necessary nexthop information,
+ * i.e. parameters of path to a destination via this nexthop.
+ *
+ * At the moment it is impossible to set different prefsrc, mtu, window
+ * and rtt for different paths from multipath.
+ */
+
+struct rtnexthop {
+	unsigned short		rtnh_len;
+	unsigned char		rtnh_flags;
+	unsigned char		rtnh_hops;
+	int			rtnh_ifindex;
+};
+
+/* rtnh_flags */
+
+#define RTNH_F_DEAD		1	/* Nexthop is dead (used by multipath)	*/
+#define RTNH_F_PERVASIVE	2	/* Do recursive gateway lookup	*/
+#define RTNH_F_ONLINK		4	/* Gateway is forced on link	*/
+
+/* Macros to handle hexthops */
+
+#define RTNH_ALIGNTO	4
+#define RTNH_ALIGN(len) ( ((len)+RTNH_ALIGNTO-1) & ~(RTNH_ALIGNTO-1) )
+#define RTNH_OK(rtnh,len) ((rtnh)->rtnh_len >= sizeof(struct rtnexthop) && \
+			   ((int)(rtnh)->rtnh_len) <= (len))
+#define RTNH_NEXT(rtnh)	((struct rtnexthop*)(((char*)(rtnh)) + RTNH_ALIGN((rtnh)->rtnh_len)))
+#define RTNH_LENGTH(len) (RTNH_ALIGN(sizeof(struct rtnexthop)) + (len))
+#define RTNH_SPACE(len)	RTNH_ALIGN(RTNH_LENGTH(len))
+#define RTNH_DATA(rtnh)   ((struct rtattr*)(((char*)(rtnh)) + RTNH_LENGTH(0)))
+
+/* RTM_CACHEINFO */
+
+struct rta_cacheinfo {
+	__u32	rta_clntref;
+	__u32	rta_lastuse;
+	__s32	rta_expires;
+	__u32	rta_error;
+	__u32	rta_used;
+
+#define RTNETLINK_HAVE_PEERINFO 1
+	__u32	rta_id;
+	__u32	rta_ts;
+	__u32	rta_tsage;
+};
+
+/* RTM_METRICS --- array of struct rtattr with types of RTAX_* */
+
+enum {
+	RTAX_UNSPEC,
+#define RTAX_UNSPEC RTAX_UNSPEC
+	RTAX_LOCK,
+#define RTAX_LOCK RTAX_LOCK
+	RTAX_MTU,
+#define RTAX_MTU RTAX_MTU
+	RTAX_WINDOW,
+#define RTAX_WINDOW RTAX_WINDOW
+	RTAX_RTT,
+#define RTAX_RTT RTAX_RTT
+	RTAX_RTTVAR,
+#define RTAX_RTTVAR RTAX_RTTVAR
+	RTAX_SSTHRESH,
+#define RTAX_SSTHRESH RTAX_SSTHRESH
+	RTAX_CWND,
+#define RTAX_CWND RTAX_CWND
+	RTAX_ADVMSS,
+#define RTAX_ADVMSS RTAX_ADVMSS
+	RTAX_REORDERING,
+#define RTAX_REORDERING RTAX_REORDERING
+	RTAX_HOPLIMIT,
+#define RTAX_HOPLIMIT RTAX_HOPLIMIT
+	RTAX_INITCWND,
+#define RTAX_INITCWND RTAX_INITCWND
+	RTAX_FEATURES,
+#define RTAX_FEATURES RTAX_FEATURES
+	RTAX_RTO_MIN,
+#define RTAX_RTO_MIN RTAX_RTO_MIN
+	RTAX_INITRWND,
+#define RTAX_INITRWND RTAX_INITRWND
+	RTAX_QUICKACK,
+#define RTAX_QUICKACK RTAX_QUICKACK
+	__RTAX_MAX
+};
+
+#define RTAX_MAX (__RTAX_MAX - 1)
+
+#define RTAX_FEATURE_ECN	0x00000001
+#define RTAX_FEATURE_SACK	0x00000002
+#define RTAX_FEATURE_TIMESTAMP	0x00000004
+#define RTAX_FEATURE_ALLFRAG	0x00000008
+
+struct rta_session {
+	__u8	proto;
+	__u8	pad1;
+	__u16	pad2;
+
+	union {
+		struct {
+			__u16	sport;
+			__u16	dport;
+		} ports;
+
+		struct {
+			__u8	type;
+			__u8	code;
+			__u16	ident;
+		} icmpt;
+
+		__u32		spi;
+	} u;
+};
+
+struct rta_mfc_stats {
+	__u64	mfcs_packets;
+	__u64	mfcs_bytes;
+	__u64	mfcs_wrong_if;
+};
+
+/****
+ *		General form of address family dependent message.
+ ****/
+
+struct rtgenmsg {
+	unsigned char		rtgen_family;
+};
+
+/*****************************************************************
+ *		Link layer specific messages.
+ ****/
+
+/* struct ifinfomsg
+ * passes link level specific information, not dependent
+ * on network protocol.
+ */
+
+struct ifinfomsg {
+	unsigned char	ifi_family;
+	unsigned char	__ifi_pad;
+	unsigned short	ifi_type;		/* ARPHRD_* */
+	int		ifi_index;		/* Link index	*/
+	unsigned	ifi_flags;		/* IFF_* flags	*/
+	unsigned	ifi_change;		/* IFF_* change mask */
+};
+
+/********************************************************************
+ *		prefix information 
+ ****/
+
+struct prefixmsg {
+	unsigned char	prefix_family;
+	unsigned char	prefix_pad1;
+	unsigned short	prefix_pad2;
+	int		prefix_ifindex;
+	unsigned char	prefix_type;
+	unsigned char	prefix_len;
+	unsigned char	prefix_flags;
+	unsigned char	prefix_pad3;
+};
+
+enum 
+{
+	PREFIX_UNSPEC,
+	PREFIX_ADDRESS,
+	PREFIX_CACHEINFO,
+	__PREFIX_MAX
+};
+
+#define PREFIX_MAX	(__PREFIX_MAX - 1)
+
+struct prefix_cacheinfo {
+	__u32	preferred_time;
+	__u32	valid_time;
+};
+
+
+/*****************************************************************
+ *		Traffic control messages.
+ ****/
+
+struct tcmsg {
+	unsigned char	tcm_family;
+	unsigned char	tcm__pad1;
+	unsigned short	tcm__pad2;
+	int		tcm_ifindex;
+	__u32		tcm_handle;
+	__u32		tcm_parent;
+	__u32		tcm_info;
+};
+
+enum {
+	TCA_UNSPEC,
+	TCA_KIND,
+	TCA_OPTIONS,
+	TCA_STATS,
+	TCA_XSTATS,
+	TCA_RATE,
+	TCA_FCNT,
+	TCA_STATS2,
+	TCA_STAB,
+	__TCA_MAX
+};
+
+#define TCA_MAX (__TCA_MAX - 1)
+
+#define TCA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcmsg))))
+#define TCA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcmsg))
+
+/********************************************************************
+ *		Neighbor Discovery userland options
+ ****/
+
+struct nduseroptmsg {
+	unsigned char	nduseropt_family;
+	unsigned char	nduseropt_pad1;
+	unsigned short	nduseropt_opts_len;	/* Total length of options */
+	int		nduseropt_ifindex;
+	__u8		nduseropt_icmp_type;
+	__u8		nduseropt_icmp_code;
+	unsigned short	nduseropt_pad2;
+	unsigned int	nduseropt_pad3;
+	/* Followed by one or more ND options */
+};
+
+enum {
+	NDUSEROPT_UNSPEC,
+	NDUSEROPT_SRCADDR,
+	__NDUSEROPT_MAX
+};
+
+#define NDUSEROPT_MAX	(__NDUSEROPT_MAX - 1)
+
+/* RTnetlink multicast groups - backwards compatibility for userspace */
+#define RTMGRP_LINK		1
+#define RTMGRP_NOTIFY		2
+#define RTMGRP_NEIGH		4
+#define RTMGRP_TC		8
+
+#define RTMGRP_IPV4_IFADDR	0x10
+#define RTMGRP_IPV4_MROUTE	0x20
+#define RTMGRP_IPV4_ROUTE	0x40
+#define RTMGRP_IPV4_RULE	0x80
+
+#define RTMGRP_IPV6_IFADDR	0x100
+#define RTMGRP_IPV6_MROUTE	0x200
+#define RTMGRP_IPV6_ROUTE	0x400
+#define RTMGRP_IPV6_IFINFO	0x800
+
+#define RTMGRP_DECnet_IFADDR    0x1000
+#define RTMGRP_DECnet_ROUTE     0x4000
+
+#define RTMGRP_IPV6_PREFIX	0x20000
+
+/* RTnetlink multicast groups */
+enum rtnetlink_groups {
+	RTNLGRP_NONE,
+#define RTNLGRP_NONE		RTNLGRP_NONE
+	RTNLGRP_LINK,
+#define RTNLGRP_LINK		RTNLGRP_LINK
+	RTNLGRP_NOTIFY,
+#define RTNLGRP_NOTIFY		RTNLGRP_NOTIFY
+	RTNLGRP_NEIGH,
+#define RTNLGRP_NEIGH		RTNLGRP_NEIGH
+	RTNLGRP_TC,
+#define RTNLGRP_TC		RTNLGRP_TC
+	RTNLGRP_IPV4_IFADDR,
+#define RTNLGRP_IPV4_IFADDR	RTNLGRP_IPV4_IFADDR
+	RTNLGRP_IPV4_MROUTE,
+#define	RTNLGRP_IPV4_MROUTE	RTNLGRP_IPV4_MROUTE
+	RTNLGRP_IPV4_ROUTE,
+#define RTNLGRP_IPV4_ROUTE	RTNLGRP_IPV4_ROUTE
+	RTNLGRP_IPV4_RULE,
+#define RTNLGRP_IPV4_RULE	RTNLGRP_IPV4_RULE
+	RTNLGRP_IPV6_IFADDR,
+#define RTNLGRP_IPV6_IFADDR	RTNLGRP_IPV6_IFADDR
+	RTNLGRP_IPV6_MROUTE,
+#define RTNLGRP_IPV6_MROUTE	RTNLGRP_IPV6_MROUTE
+	RTNLGRP_IPV6_ROUTE,
+#define RTNLGRP_IPV6_ROUTE	RTNLGRP_IPV6_ROUTE
+	RTNLGRP_IPV6_IFINFO,
+#define RTNLGRP_IPV6_IFINFO	RTNLGRP_IPV6_IFINFO
+	RTNLGRP_DECnet_IFADDR,
+#define RTNLGRP_DECnet_IFADDR	RTNLGRP_DECnet_IFADDR
+	RTNLGRP_NOP2,
+	RTNLGRP_DECnet_ROUTE,
+#define RTNLGRP_DECnet_ROUTE	RTNLGRP_DECnet_ROUTE
+	RTNLGRP_DECnet_RULE,
+#define RTNLGRP_DECnet_RULE	RTNLGRP_DECnet_RULE
+	RTNLGRP_NOP4,
+	RTNLGRP_IPV6_PREFIX,
+#define RTNLGRP_IPV6_PREFIX	RTNLGRP_IPV6_PREFIX
+	RTNLGRP_IPV6_RULE,
+#define RTNLGRP_IPV6_RULE	RTNLGRP_IPV6_RULE
+	RTNLGRP_ND_USEROPT,
+#define RTNLGRP_ND_USEROPT	RTNLGRP_ND_USEROPT
+	RTNLGRP_PHONET_IFADDR,
+#define RTNLGRP_PHONET_IFADDR	RTNLGRP_PHONET_IFADDR
+	RTNLGRP_PHONET_ROUTE,
+#define RTNLGRP_PHONET_ROUTE	RTNLGRP_PHONET_ROUTE
+	RTNLGRP_DCB,
+#define RTNLGRP_DCB		RTNLGRP_DCB
+	RTNLGRP_IPV4_NETCONF,
+#define RTNLGRP_IPV4_NETCONF	RTNLGRP_IPV4_NETCONF
+	RTNLGRP_IPV6_NETCONF,
+#define RTNLGRP_IPV6_NETCONF	RTNLGRP_IPV6_NETCONF
+	RTNLGRP_MDB,
+#define RTNLGRP_MDB		RTNLGRP_MDB
+	__RTNLGRP_MAX
+};
+#define RTNLGRP_MAX	(__RTNLGRP_MAX - 1)
+
+/* TC action piece */
+struct tcamsg {
+	unsigned char	tca_family;
+	unsigned char	tca__pad1;
+	unsigned short	tca__pad2;
+};
+#define TA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcamsg))))
+#define TA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcamsg))
+#define TCA_ACT_TAB 1 /* attr type must be >=1 */	
+#define TCAA_MAX 1
+
+/* New extended info filters for IFLA_EXT_MASK */
+#define RTEXT_FILTER_VF		(1 << 0)
+#define RTEXT_FILTER_BRVLAN	(1 << 1)
+
+/* End of information exported to user level */
+
+
+
+#endif /* __LINUX_RTNETLINK_H */
diff --git a/package/gluon-site/Makefile b/package/gluon-site/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..fc9f58ce379ea31b4dadd27741462aa9865642ee
--- /dev/null
+++ b/package/gluon-site/Makefile
@@ -0,0 +1,42 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-site
+PKG_VERSION:=$(if $(GLUON_SITE_CODE),$(GLUON_SITE_CODE),1)
+PKG_RELEASE:=$(GLUON_RELEASE)
+
+PKG_FILE_DEPENDS := $(GLUON_SITEDIR)/
+PKG_BUILD_DEPENDS := luci
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-site
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Site-specific files of Gluon
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-site,$(GLUON_SITEDIR)/i18n)
+endef
+
+define Package/gluon-site/install
+	$(INSTALL_DIR) $(1)/lib/gluon
+	$(CP) $(GLUON_SITEDIR)/site.conf $(1)/lib/gluon/site.conf
+	echo "$(GLUON_RELEASE)" > $(1)/lib/gluon/release
+
+	$(call GluonInstallI18N,gluon-site,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-site))
diff --git a/package/gluon-status-page/Makefile b/package/gluon-status-page/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4e44e2b29d96e08bdcd7e20d19af76158b7ab237
--- /dev/null
+++ b/package/gluon-status-page/Makefile
@@ -0,0 +1,37 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-status-page
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-status-page
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Adds a status page showing information about the node.
+  DEPENDS:=+gluon-core +gluon-neighbour-info +uhttpd
+endef
+
+define Package/gluon-status-page/description
+	Adds a status page showing information about the node.
+	Especially useful in combination with the next-node feature.
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-status-page/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-status-page))
diff --git a/package/gluon-status-page/files/lib/gluon/status-page/www/cgi-bin/status b/package/gluon-status-page/files/lib/gluon/status-page/www/cgi-bin/status
new file mode 100755
index 0000000000000000000000000000000000000000..23e612d32870576626843530cc7a56029b2d3d76
--- /dev/null
+++ b/package/gluon-status-page/files/lib/gluon/status-page/www/cgi-bin/status
@@ -0,0 +1,147 @@
+#!/usr/bin/lua
+
+local util = require("luci.util")
+local fs = require("luci.fs")
+local ltn12 = require 'luci.ltn12'
+local sys = require("luci.sys")
+local json = require("luci.json")
+local nixio = require 'nixio'
+local platform_info = require("platform_info")
+
+local hostname = sys.hostname()
+local model = platform_info.get_model()
+local release = util.trim(fs.readfile("/lib/gluon/release") or "")
+
+function escape_html(s)
+  return (s:gsub('&', '&amp;'):gsub('<', '&lt;'):gsub('>', '&gt;'):gsub('"', '&quot;'))
+end
+
+function neighbours(ifname)
+  local info = util.exec("gluon-neighbour-info -d ff02::2:1001 -p 1001 -r nodeinfo -t 3 -i " .. ifname)
+  local macs = {}
+  for _, line in ipairs(util.split(info)) do
+    local data = json.decode(line)
+    if data then
+      if data["network"] and data["network"]["mesh_interfaces"] then
+        for _, mac in ipairs(data["network"]["mesh_interfaces"]) do
+          macs[mac] = data
+        end
+      end
+    end
+  end
+
+  return macs
+end
+
+io.write("Content-type: text/html\n\n")
+io.write("<!DOCTYPE html>\n")
+io.write("<html>")
+io.write("<head>")
+io.write("<script src=\"/status.js\"></script>")
+io.write("<title>" .. escape_html(hostname) .. "</title>")
+io.write("</head>")
+io.write("<body>")
+
+io.write("<h1>" .. escape_html(hostname) .. "</h1>")
+io.write("<pre>")
+
+io.write("Model: " .. escape_html(model) .. "\n")
+io.write("Firmware release: " .. escape_html(release) .. "\n\n")
+
+io.write(escape_html(util.trim(sys.exec("uptime | sed 's/^ \+//'"))) .. "\n\n")
+io.write(escape_html(sys.exec("ip address show dev br-client")) .. "\n")
+io.write(escape_html(sys.exec("free -m")) .. "\n")
+io.write(escape_html(sys.exec("df /rom /overlay")))
+io.write("</pre>")
+
+io.write("<h2>Neighbours</h2>")
+
+local interfaces = util.split(util.trim(util.exec("iw dev | grep IBSS -B 5 | grep Interface | cut -d' ' -f2")))
+
+for _, ifname in ipairs(interfaces) do
+  io.write("<h3>" .. escape_html(ifname) .. "</h3>")
+  io.write("<pre>")
+
+  io.write(escape_html(sys.exec("iw dev " .. ifname .. " link")) .. "\n")
+
+  for _, line in ipairs(util.split(util.exec("iw dev " .. ifname .. " station dump"))) do
+    local mac = line:match("^Station (.*) %(on ")
+    if mac then
+      io.write("Station <a id=\"" .. escape_html(ifname) .. "-" .. mac .. "\">" .. mac .. "</a> (on " .. escape_html(ifname) .. ")\n")
+    else
+      io.write(escape_html(line) .. "\n")
+    end
+  end
+
+  io.write("</pre>")
+end
+
+local stat, fastd_status = pcall(
+  function()
+    local fastd_sock = nixio.socket('unix', 'stream')
+    assert(fastd_sock:connect('/var/run/fastd.mesh_vpn.socket'))
+
+    decoder = json.Decoder()
+    ltn12.pump.all(ltn12.source.file(fastd_sock), decoder:sink())
+    return decoder:get()
+  end
+)
+
+io.write("<h2>VPN status</h2>")
+io.write("<pre>")
+
+if stat then
+  io.write(string.format("fastd running for %.3f seconds\n", fastd_status.uptime/1000))
+
+  local peers = 0
+  local connections = 0
+
+  for key, peer in pairs(fastd_status.peers) do
+    peers = peers+1
+
+    if peer.connection then
+      connections = connections+1
+    end
+  end
+
+  io.write(string.format("There are %i peers configured, of which %i are connected:\n\n", peers, connections))
+
+  for key, peer in pairs(fastd_status.peers) do
+    io.write(string.format("%s: ", escape_html(peer.name)))
+
+    if peer.connection then
+      io.write(string.format("connected for %.3f seconds\n", peer.connection.established/1000))
+    else
+      io.write("not connected\n")
+    end
+  end
+
+else
+  io.write("fastd not running")
+end
+
+io.write("</pre>")
+
+io.write("<script>")
+for _, ifname in ipairs(interfaces) do
+  local macs = neighbours(ifname)
+  for mac, node in pairs(macs) do
+    local hostname = node["hostname"]
+    local ip
+    if node["network"] and node["network"]["addresses"] then
+      for _, myip in ipairs(node["network"]["addresses"]) do
+        if ip == nil and myip:sub(1, 5) ~= "fe80:" then
+          ip = myip
+        end
+      end
+    end
+
+    if ip and hostname then
+      io.write("update_node(\"" .. escape_html(ifname) .. "-" .. mac .. "\", \"" .. escape_html(ip) .. "\", \"" .. escape_html(hostname) .. "\");")
+    end
+  end
+end
+
+io.write("</script>")
+io.write("</body>")
+io.write("</html>")
diff --git a/package/gluon-status-page/files/lib/gluon/status-page/www/index.html b/package/gluon-status-page/files/lib/gluon/status-page/www/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..75700015a4b45b660afba4e55d828b90c1df150c
--- /dev/null
+++ b/package/gluon-status-page/files/lib/gluon/status-page/www/index.html
@@ -0,0 +1,12 @@
+<html>
+  <head>
+    <meta http-equiv="refresh" content="0; URL=/cgi-bin/status">
+    <meta http-equiv="cache-control" content="no-cache">
+    <meta http-equiv="expires" content="0">
+    <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT">
+    <meta http-equiv="pragma" content="no-cache">
+  </head>
+  <body>
+    <a href="/cgi-bin/status">Redirecting...</a>
+  </body>
+</html>
diff --git a/package/gluon-status-page/files/lib/gluon/status-page/www/status.js b/package/gluon-status-page/files/lib/gluon/status-page/www/status.js
new file mode 100644
index 0000000000000000000000000000000000000000..e17102d004a0b69273ba6f2c0f28b09296b08ce0
--- /dev/null
+++ b/package/gluon-status-page/files/lib/gluon/status-page/www/status.js
@@ -0,0 +1,9 @@
+function update_node(id, ip, hostname) {
+  var el = document.getElementById(id);
+
+  if (!el)
+    return;
+
+  el.href = "http://[" + ip + "]/";
+  el.textContent += " (" + hostname + ")";
+}
diff --git a/package/gluon-status-page/files/lib/gluon/upgrade/500-status-page b/package/gluon-status-page/files/lib/gluon/upgrade/500-status-page
new file mode 100755
index 0000000000000000000000000000000000000000..ee7a58c90661b0d2d8848a3482e6af881c1e57da
--- /dev/null
+++ b/package/gluon-status-page/files/lib/gluon/upgrade/500-status-page
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+uci batch <<-EOF
+	delete uhttpd.main.listen_http
+	add_list uhttpd.main.listen_http=0.0.0.0:80
+	add_list uhttpd.main.listen_http=[::]:80
+
+	delete uhttpd.main.listen_https
+
+	set uhttpd.main.home=/lib/gluon/status-page/www
+
+	commit uhttpd
+EOF
diff --git a/package/gluon-wan-dnsmasq/Makefile b/package/gluon-wan-dnsmasq/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3722210f9be03626a646266cb1b008d3c540b605
--- /dev/null
+++ b/package/gluon-wan-dnsmasq/Makefile
@@ -0,0 +1,35 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-wan-dnsmasq
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-wan-dnsmasq
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Support for a secondary DNS server using the WAN interface
+  DEPENDS:=+gluon-core +dnsmasq +libpacketmark
+endef
+
+define Package/gluon-wan-dnsmasq/description
+	Gluon community wifi mesh firmware framework: Support for a secondary DNS server using the WAN interface
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-wan-dnsmasq/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-wan-dnsmasq))
diff --git a/package/gluon-wan-dnsmasq/files/etc/config/gluon-wan-dnsmasq b/package/gluon-wan-dnsmasq/files/etc/config/gluon-wan-dnsmasq
new file mode 100644
index 0000000000000000000000000000000000000000..9c7e9b4c3b836fd3ce3ed646e600fa35332b9230
--- /dev/null
+++ b/package/gluon-wan-dnsmasq/files/etc/config/gluon-wan-dnsmasq
@@ -0,0 +1,2 @@
+config 'static'
+	# list 'server' '192.168.0.1'		# Example
diff --git a/package/gluon-wan-dnsmasq/files/etc/hotplug.d/iface/50-gluon-wan-dnsmasq b/package/gluon-wan-dnsmasq/files/etc/hotplug.d/iface/50-gluon-wan-dnsmasq
new file mode 100644
index 0000000000000000000000000000000000000000..d559e9b13274dd709ae364d5ed8f463f6a91d90f
--- /dev/null
+++ b/package/gluon-wan-dnsmasq/files/etc/hotplug.d/iface/50-gluon-wan-dnsmasq
@@ -0,0 +1,3 @@
+if [ "$INTERFACE" = 'wan' -o "$INTERFACE" = 'wan6' ]; then
+   /lib/gluon/wan-dnsmasq/update.lua
+fi
diff --git a/package/gluon-wan-dnsmasq/files/etc/init.d/gluon-wan-dnsmasq b/package/gluon-wan-dnsmasq/files/etc/init.d/gluon-wan-dnsmasq
new file mode 100755
index 0000000000000000000000000000000000000000..22bed0392f7e3f58d0f5bcd0e07a938e7b1abde5
--- /dev/null
+++ b/package/gluon-wan-dnsmasq/files/etc/init.d/gluon-wan-dnsmasq
@@ -0,0 +1,26 @@
+#!/bin/sh /etc/rc.common
+
+START=60
+
+SERVICE_NAME=gluon-wan-dnsmasq
+SERVICE_USE_PID=1
+SERVICE_PID_FILE=/var/run/gluon-wan-dnsmasq.pid
+
+
+PORT=54
+PACKET_MARK=1
+
+RESOLV_CONF_DIR=/var/gluon/wan-dnsmasq
+RESOLV_CONF=$RESOLV_CONF_DIR/resolv.conf
+
+
+start() {
+	mkdir -p $RESOLV_CONF_DIR
+	/lib/gluon/wan-dnsmasq/update.lua
+
+	LD_PRELOAD=libpacketmark.so LIBPACKETMARK_MARK=$PACKET_MARK service_start /usr/sbin/dnsmasq -x $SERVICE_PID_FILE -u root -i lo -p $PORT -h -r $RESOLV_CONF
+}
+
+stop() {
+	service_stop /usr/sbin/dnsmasq
+}
diff --git a/package/gluon-wan-dnsmasq/files/lib/gluon/wan-dnsmasq/update.lua b/package/gluon-wan-dnsmasq/files/lib/gluon/wan-dnsmasq/update.lua
new file mode 100755
index 0000000000000000000000000000000000000000..88a86507a2735899f9cddb2e40179a4eaf694114
--- /dev/null
+++ b/package/gluon-wan-dnsmasq/files/lib/gluon/wan-dnsmasq/update.lua
@@ -0,0 +1,46 @@
+#!/usr/bin/lua
+
+local RESOLV_CONF_DIR = '/var/gluon/wan-dnsmasq'
+local RESOLV_CONF = RESOLV_CONF_DIR .. '/resolv.conf'
+
+
+local ubus = require('ubus').connect()
+local uci = require('luci.model.uci').cursor()
+local fs = require 'nixio.fs'
+
+
+local new_servers = ''
+
+
+local function append_servers(servers)
+  for _, server in ipairs(servers) do
+    new_servers = new_servers .. 'nameserver ' .. server .. '\n'
+  end
+end
+
+local function append_interface_servers(iface)
+  append_servers(ubus:call('network.interface.' .. iface, 'status', {}).inactive['dns-server'])
+end
+
+
+local static = uci:get_first('gluon-wan-dnsmasq', 'static', 'server')
+
+if type(static) == 'table' and #static > 0 then
+  append_servers(static)
+else
+  pcall(append_interface_servers, 'wan6')
+  pcall(append_interface_servers, 'wan')
+end
+
+
+fs.mkdirr(RESOLV_CONF_DIR)
+
+local old_servers = fs.readfile(RESOLV_CONF)
+
+if new_servers ~= old_servers then
+   local f = io.open(RESOLV_CONF .. '.tmp', 'w')
+   f:write(new_servers)
+   f:close()
+
+   fs.rename(RESOLV_CONF .. '.tmp', RESOLV_CONF)
+end