diff --git a/contrib/ci/olsr-site/i18n b/contrib/ci/olsr-site/i18n
new file mode 120000
index 0000000000000000000000000000000000000000..57a3c3977ac165a8c413fcebee2a9840e564e26f
--- /dev/null
+++ b/contrib/ci/olsr-site/i18n
@@ -0,0 +1 @@
+../minimal-site/i18n
\ No newline at end of file
diff --git a/contrib/ci/olsr-site/modules b/contrib/ci/olsr-site/modules
new file mode 120000
index 0000000000000000000000000000000000000000..72d21398d0d4711668d34d5f58668993a6eaec99
--- /dev/null
+++ b/contrib/ci/olsr-site/modules
@@ -0,0 +1 @@
+../minimal-site/modules
\ No newline at end of file
diff --git a/contrib/ci/olsr-site/site.conf b/contrib/ci/olsr-site/site.conf
new file mode 100644
index 0000000000000000000000000000000000000000..868d3d569d665f6d99335dde4f9a9c4115ade727
--- /dev/null
+++ b/contrib/ci/olsr-site/site.conf
@@ -0,0 +1,176 @@
+-- This is an example site configuration for Gluon v2022.1
+--
+-- Take a look at the documentation located at
+-- https://gluon.readthedocs.io/ for details.
+--
+-- This configuration will not work as is. You're required to make
+-- community specific changes to it!
+{
+  -- Used for generated hostnames, e.g. freifunk-abcdef123456. (optional)
+  -- hostname_prefix = 'freifunk-',
+
+  -- Name of the community.
+  site_name = 'Continuous Integration',
+
+  -- Shorthand of the community.
+  site_code = 'ci',
+
+  -- 32 bytes of random data, encoded in hexadecimal
+  -- This data must be unique among all sites and domains!
+  -- Can be generated using: echo $(hexdump -v -n 32 -e '1/1 "%02x"' </dev/urandom)
+  domain_seed = 'e9608c4ff338b920992d629190e9ff11049de1dfc3f299eac07792dfbcda341c',
+
+  -- Prefixes used by clients within the mesh.
+  -- prefix6 is required, prefix4 can be omitted if next_node.ip4
+  -- is not set.
+  prefix6 = 'fdff:cafe:cafe:cafe::/64',
+
+  -- Prefixes used by nodes within the mesh
+  node_prefix6 = 'fdff:cafe:cafe:cafe::/64',
+
+  -- Timezone of your community.
+  -- See https://openwrt.org/docs/guide-user/base-system/system_configuration#time_zones
+  timezone = 'CET-1CEST,M3.5.0,M10.5.0/3',
+
+  -- List of NTP servers in your community.
+  -- Must be reachable using IPv6!
+  --  ntp_servers = {'1.ntp.services.ffxx'},
+
+  -- Wireless regulatory domain of your community.
+  regdom = 'DE',
+
+  -- Wireless configuration for 2.4 GHz interfaces.
+  wifi24 = {
+    -- Wireless channel.
+    channel = 1,
+
+    -- ESSIDs used for client network.
+    ap = {
+      ssid = 'gluon-ci-ssid',
+      -- disabled = true, -- (optional)
+
+      -- Configuration for a backward compatible OWE network below.
+      owe_ssid = 'owe.gluon-ci-ssid', -- (optional - SSID for OWE client network)
+      owe_transition_mode = true, -- (optional - enables transition-mode - requires ssid as well as owe_ssid)
+    },
+
+    mesh = {
+      -- Adjust these values!
+      id = 'ueH3uXjdp', -- usually you don't want users to connect to this mesh-SSID, so use a cryptic id that no one will accidentally mistake for the client WiFi
+      mcast_rate = 12000,
+      -- disabled = true, -- (optional)
+    },
+  },
+
+  -- Wireless configuration for 5 GHz interfaces.
+  -- This should be equal to the 2.4 GHz variant, except
+  -- for channel.
+  wifi5 = {
+    channel = 44,
+    outdoor_chanlist = '100-140',
+    ap = {
+      ssid = 'gluon-ci-ssid',
+      -- disabled = true, -- (optional)
+
+      -- Configuration for a backward compatible OWE network below.
+      owe_ssid = 'owe.gluon-ci-ssid', -- (optional - SSID for OWE client network)
+      owe_transition_mode = true, -- (optional - enables transition-mode - requires ssid as well as owe_ssid)
+    },
+    mesh = {
+      -- Adjust these values!
+      id = 'ueH3uXjdp',
+      mcast_rate = 12000,
+    },
+  },
+
+
+  -- The next node feature allows clients to always reach the node it is
+  -- connected to using a known IP address.
+  next_node = {
+    -- anycast IPs of all nodes
+    name = { 'nextnode.location.community.example.org', 'nextnode', 'nn' },
+    ip4 = '10.0.0.1',
+    ip6 = 'fd::1',
+  },
+
+  -- Options specific to routing protocols (optional)
+  mesh = {
+    vxlan = true,
+    olsrd = {},
+  },
+
+  mesh_vpn = {
+    -- enabled = true,
+
+    fastd = {
+      -- Refer to https://fastd.readthedocs.io/en/latest/ to better understand
+      -- what these options do.
+
+      -- List of crypto-methods to use.
+      methods = {'salsa2012+umac'},
+      mtu = 1312,
+      -- configurable = true,
+      -- syslog_level = 'warn',
+
+      groups = {
+        backbone = {
+          -- Limit number of connected peers to reduce bandwidth.
+          limit = 1,
+
+          -- List of peers.
+          peers = {
+          },
+
+          -- Optional: nested peer groups
+          -- groups = {
+            -- backbone_sub = {
+              -- ...
+            -- },
+          -- ...
+          -- },
+        },
+        -- Optional: additional peer groups, possibly with other limits
+        -- backbone2 = {
+          -- ...
+        -- },
+      },
+    },
+
+    bandwidth_limit = {
+      -- The bandwidth limit can be enabled by default here.
+      enabled = false,
+
+      -- Default upload limit (kbit/s).
+      egress = 200,
+
+      -- Default download limit (kbit/s).
+      ingress = 3000,
+    },
+  },
+
+  autoupdater = {
+    -- Default branch (optional), can be overridden by setting GLUON_AUTOUPDATER_BRANCH when building.
+    -- Set GLUON_AUTOUPDATER_ENABLED to enable the autoupdater by default for newly installed nodes.
+    branch = 'stable',
+
+    -- List of branches. You may define multiple branches.
+    branches = {
+      stable = {
+        name = 'stable',
+
+        -- List of mirrors to fetch images from. IPv6 required!
+        mirrors = {'http://1.updates.services.ffhl/stable/sysupgrade'},
+
+        -- Number of good signatures required.
+        -- Have multiple maintainers sign your build and only
+        -- accept it when a sufficient number of them have
+        -- signed it.
+        good_signatures = 0,
+
+        -- List of public keys of maintainers.
+        pubkeys = {
+        },
+      },
+    },
+  },
+}
diff --git a/contrib/ci/olsr-site/site.mk b/contrib/ci/olsr-site/site.mk
new file mode 100644
index 0000000000000000000000000000000000000000..8b4a5a434a5f8415c03dfaef8239d03f3686f103
--- /dev/null
+++ b/contrib/ci/olsr-site/site.mk
@@ -0,0 +1,57 @@
+##	gluon site.mk makefile example
+
+##	GLUON_FEATURES
+#		Specify Gluon features/packages to enable;
+#		Gluon will automatically enable a set of packages
+#		depending on the combination of features listed
+
+GLUON_FEATURES := \
+	autoupdater \
+	ebtables-filter-multicast \
+	ebtables-filter-ra-dhcp \
+	ebtables-limit-arp \
+	mesh-olsrd \
+	mesh-vpn-fastd \
+	respondd \
+	status-page \
+	web-advanced \
+	web-wizard
+
+GLUON_FEATURES_standard := \
+  wireless-encryption-wpa3
+
+##	GLUON_SITE_PACKAGES
+#		Specify additional Gluon/OpenWrt packages to include here;
+#		A minus sign may be prepended to remove a packages from the
+#		selection that would be enabled by default or due to the
+#		chosen feature flags
+
+GLUON_SITE_PACKAGES := iwinfo
+
+##	DEFAULT_GLUON_RELEASE
+#		version string to use for images
+#		gluon relies on
+#			opkg compare-versions "$1" '>>' "$2"
+#		to decide if a version is newer or not.
+
+DEFAULT_GLUON_RELEASE := 0.6+exp$(shell date '+%Y%m%d')
+
+# Variables set with ?= can be overwritten from the command line
+
+##	GLUON_RELEASE
+#		call make with custom GLUON_RELEASE flag, to use your own release version scheme.
+#		e.g.:
+#			$ make images GLUON_RELEASE=23.42+5
+#		would generate images named like this:
+#			gluon-ff%site_code%-23.42+5-%router_model%.bin
+
+GLUON_RELEASE ?= $(DEFAULT_GLUON_RELEASE)
+
+# Default priority for updates.
+GLUON_PRIORITY ?= 0
+
+# Region code required for some images; supported values: us eu
+GLUON_REGION ?= eu
+
+# Languages to include
+GLUON_LANGS ?= en de
diff --git a/package/gluon-mesh-olsrd/Makefile b/package/gluon-mesh-olsrd/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..adf1a07a4bc512e8ebf2902ab754e684313bb13d
--- /dev/null
+++ b/package/gluon-mesh-olsrd/Makefile
@@ -0,0 +1,19 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-mesh-olsrd
+PKG_VERSION=1
+
+include ../gluon.mk
+
+define Package/gluon-mesh-olsrd
+	TITLE:=olsrd mesh
+	DEPENDS:= \
+		+gluon-core \
+		@IPV6 \
+		+oonf-olsrd2 \
+		+firewall \
+		+gluon-mesh-layer3-common
+	PROVIDES:=gluon-mesh-provider
+endef
+
+$(eval $(call BuildPackageGluon,gluon-mesh-olsrd))
diff --git a/package/gluon-mesh-olsrd/check_site.lua b/package/gluon-mesh-olsrd/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..3e13679068412282bdc3b4eb9ab9a881127d6f65
--- /dev/null
+++ b/package/gluon-mesh-olsrd/check_site.lua
@@ -0,0 +1 @@
+need_table({'mesh', 'olsrd', 'v2', 'config'}, nil, false)
diff --git a/package/gluon-mesh-olsrd/files/lib/gluon/core/mesh/post-setup.d/30-reload-olsr b/package/gluon-mesh-olsrd/files/lib/gluon/core/mesh/post-setup.d/30-reload-olsr
new file mode 100755
index 0000000000000000000000000000000000000000..e96dcce2c6913b8dd64f94d884b0024e67002543
--- /dev/null
+++ b/package/gluon-mesh-olsrd/files/lib/gluon/core/mesh/post-setup.d/30-reload-olsr
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+reload_running() {
+	if [ -x /etc/init.d/"$1" ] && /etc/init.d/"$1" enabled && /etc/init.d/"$1" running; then
+		echo "(post-setup.d:$IFNAME) Reloading $1..."
+		/etc/init.d/"$1" reload
+	fi
+}
+
+reload_running olsrd2
diff --git a/package/gluon-mesh-olsrd/files/lib/gluon/core/mesh/teardown.d/70-reload-olsr b/package/gluon-mesh-olsrd/files/lib/gluon/core/mesh/teardown.d/70-reload-olsr
new file mode 100755
index 0000000000000000000000000000000000000000..0eb0e58caa437c20a1212dc92e13b8322cbe5d8a
--- /dev/null
+++ b/package/gluon-mesh-olsrd/files/lib/gluon/core/mesh/teardown.d/70-reload-olsr
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+reload_running() {
+	if [ -x /etc/init.d/"$1" ] && /etc/init.d/"$1" enabled && /etc/init.d/"$1" running; then
+		echo "(teardown.d:$IFNAME) Reloading $1..."
+		/etc/init.d/"$1" reload
+	fi
+}
+
+reload_running olsrd2
diff --git a/package/gluon-mesh-olsrd/files/lib/gluon/respondd/client.dev b/package/gluon-mesh-olsrd/files/lib/gluon/respondd/client.dev
new file mode 100644
index 0000000000000000000000000000000000000000..202a2374ae5a6d18c57d5f0ed33e89bc87abb6bc
--- /dev/null
+++ b/package/gluon-mesh-olsrd/files/lib/gluon/respondd/client.dev
@@ -0,0 +1 @@
+mmfd
diff --git a/package/gluon-mesh-olsrd/files/usr/lib/autoupdater/abort.d/10olsrd b/package/gluon-mesh-olsrd/files/usr/lib/autoupdater/abort.d/10olsrd
new file mode 100755
index 0000000000000000000000000000000000000000..8f39df99445b353599276f54fb1ef3567b0604d0
--- /dev/null
+++ b/package/gluon-mesh-olsrd/files/usr/lib/autoupdater/abort.d/10olsrd
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+. /lib/gluon/autoupdater/lib.sh
+
+
+start_enabled olsrd2
+wifi up
diff --git a/package/gluon-mesh-olsrd/files/usr/lib/autoupdater/upgrade.d/10olsrd b/package/gluon-mesh-olsrd/files/usr/lib/autoupdater/upgrade.d/10olsrd
new file mode 100755
index 0000000000000000000000000000000000000000..c9cd9a8ccaba509d9bc38c18196df7a61333b59e
--- /dev/null
+++ b/package/gluon-mesh-olsrd/files/usr/lib/autoupdater/upgrade.d/10olsrd
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+. /lib/gluon/autoupdater/lib.sh
+
+
+stop olsrd2
+wifi down
diff --git a/package/gluon-mesh-olsrd/luasrc/lib/gluon/upgrade/360-gluon-mesh-olsrd-setup-intf b/package/gluon-mesh-olsrd/luasrc/lib/gluon/upgrade/360-gluon-mesh-olsrd-setup-intf
new file mode 100755
index 0000000000000000000000000000000000000000..880dc31fd4baee8d29abb5133e18ded309fdd895
--- /dev/null
+++ b/package/gluon-mesh-olsrd/luasrc/lib/gluon/upgrade/360-gluon-mesh-olsrd-setup-intf
@@ -0,0 +1,163 @@
+#!/usr/bin/lua
+
+local uci = require('simple-uci').cursor()
+local site = require 'gluon.site'
+local util = require 'gluon.util'
+local wireless = require 'gluon.wireless'
+local mesh_interfaces = util.get_role_interfaces(uci, 'mesh')
+local uplink_interfaces = util.get_role_interfaces(uci, 'uplink')
+local client_interfaces = util.get_role_interfaces(uci, 'client')
+
+local mesh_interfaces_uplink = {}
+local mesh_interfaces_client = {}
+local mesh_interfaces_other = {}
+for _, iface in ipairs(mesh_interfaces) do
+	if util.contains(uplink_interfaces, iface) then
+		table.insert(mesh_interfaces_uplink, iface)
+	elseif util.contains(client_interfaces, iface) then
+		table.insert(mesh_interfaces_client, iface)
+	else
+		table.insert(mesh_interfaces_other, iface)
+	end
+end
+
+local intf = {
+	wired_mesh = {},
+	vpn_mesh = {},
+	radio_mesh = {},
+}
+
+intf.all_intfs = {}
+
+for _, l in ipairs({ intf.wired_mesh, intf.vpn_mesh, intf.radio_mesh }) do
+	for _, n in ipairs(l) do
+		table.insert(intf.all_intfs, n)
+	end
+end
+
+-- get all mesh radios and mesh lans and then add them to olsrd
+wireless.foreach_radio(uci, function(radio, _, _)
+	local radio_name = radio['.name']
+	table.insert(intf.radio_mesh, 'mesh_' .. radio_name)
+end)
+
+if pcall(function() require 'gluon.mesh-vpn' end) then
+	local vpn_core = require 'gluon.mesh-vpn'
+
+	if vpn_core.enabled() then
+		-- mesh_vpn is a interface that has the right ifname
+		-- we can't use mesh-vpn (dash instead of underscore) since it's not a uci interface
+		table.insert(intf.vpn_mesh, 'mesh_vpn')
+	end
+end
+
+table.insert(intf.wired_mesh, 'loopback')
+
+local has_uplink_mesh = false
+local has_other_mesh = false
+
+for _,i in pairs(mesh_interfaces) do
+	if util.contains(uplink_interfaces, i) then
+		has_uplink_mesh = true
+	else
+		has_other_mesh = true
+	end
+end
+
+if has_uplink_mesh then
+	table.insert(intf.wired_mesh, 'mesh_uplink')
+end
+
+if has_other_mesh then
+	table.insert(intf.wired_mesh, 'mesh_other')
+end
+
+uci:delete_all('olsrd2', 'interface')
+
+if site.mesh.olsrd.v2.enable(true) then
+	os.execute('/etc/init.d/olsrd2 enable')
+
+	local addrs = { }
+	local lan = { }
+	local cfg = site.mesh.olsrd.v2
+	local config = uci:get_first("olsrd2", "olsrv2")
+
+	-- set global config
+	local olsr2Config = {
+		failfast = 'no',
+		pidfile = '/var/run/olsrd2.pid',
+		lockfile = '/var/lock/olsrd2'
+	}
+
+	local extraConf = cfg.config()
+	if extraConf then
+		for k, _ in pairs(extraConf) do
+			olsr2Config[k] = extraConf[k]
+		end
+	end
+
+	uci:delete_all('olsrd2', 'global')
+	uci:section('olsrd2', 'global', 'global', olsr2Config)
+
+	uci:delete_all('olsrd2', 'telnet')
+	uci:section('olsrd2', 'telnet', 'telnet', {
+
+	})
+
+	uci:delete_all('olsrd2', 'http')
+	uci:section('olsrd2', 'http', 'http', {
+
+	})
+
+	if cfg.lan() then
+		lan = cfg.lan()
+	end
+
+	table.insert(addrs, '-127.0.0.1/8')
+	table.insert(addrs, '-::1/128')
+
+	table.insert(addrs, 'default_accept')
+
+	uci:set("olsrd2", config, "originator", addrs)
+	uci:set("olsrd2", config, "lan", lan)
+
+	if #intf.wired_mesh then
+		uci:section('olsrd2', 'interface', 'wired_mesh', {
+			ifname = intf.wired_mesh,
+			bindto = addrs,
+		})
+	end
+
+	if #intf.vpn_mesh then
+		uci:section('olsrd2', 'interface', 'vpn_mesh', {
+			ifname = intf.vpn_mesh,
+			bindto = addrs,
+		})
+	end
+
+	if #intf.radio_mesh then
+		uci:section('olsrd2', 'interface', 'radio_mesh', {
+			ifname = intf.radio_mesh,
+			bindto = addrs,
+		})
+	end
+
+	uci:section('olsrd2', 'interface', 'loopback', {
+		ifname = { 'loopback' },
+		bindto = addrs,
+	})
+
+	uci:section('firewall', 'rule', 'allow_olsr2_mesh', {
+		src = 'mesh',
+		dest_port = '269',
+		proto = 'udp',
+		target = 'ACCEPT',
+	})
+else
+	-- site.mesh.olsrd.v2.enable false
+	os.execute('/etc/init.d/olsrd2 disable')
+	uci:delete('firewall', 'allow_olsr2_mesh')
+end
+uci:save('olsrd2')
+uci:save('firewall')
+uci:save('network')
diff --git a/package/gluon-mesh-olsrd/luasrc/lib/gluon/upgrade/370-gluon-mesh-olsrd-setup-fw b/package/gluon-mesh-olsrd/luasrc/lib/gluon/upgrade/370-gluon-mesh-olsrd-setup-fw
new file mode 100755
index 0000000000000000000000000000000000000000..65387e8755b48f16ef92657bfbdad4a7ba22d0d7
--- /dev/null
+++ b/package/gluon-mesh-olsrd/luasrc/lib/gluon/upgrade/370-gluon-mesh-olsrd-setup-fw
@@ -0,0 +1,10 @@
+#!/usr/bin/lua
+
+local uci = require('simple-uci').cursor()
+local util = require 'gluon.util'
+
+local networks = uci:get_list('firewall', 'drop', 'network')
+util.remove_from_set(networks, 'client')
+uci:set_list('firewall', 'drop', 'network', networks)
+
+uci:save('firewall')
diff --git a/patches/packages/routing/0004-oonf-olsrd2-add-support-to-check-if-service-is-running.patch b/patches/packages/routing/0004-oonf-olsrd2-add-support-to-check-if-service-is-running.patch
new file mode 100644
index 0000000000000000000000000000000000000000..4b8c9aad769d693440b910ebdbbbc6400ff4368d
--- /dev/null
+++ b/patches/packages/routing/0004-oonf-olsrd2-add-support-to-check-if-service-is-running.patch
@@ -0,0 +1,20 @@
+From: Maciej Krüger <mkg20001@gmail.com>
+Date: Sun, 10 Apr 2022 01:58:41 +0200
+Subject: oonf-olsrd2: add support to check if service is running
+
+diff --git a/oonf-olsrd2/files/olsrd2.init b/oonf-olsrd2/files/olsrd2.init
+index debae9883258b821a5ea0aecebe879ddc84e29eb..b6c1e9a5522788005db850ceaf6699aa1eee6877 100755
+--- a/oonf-olsrd2/files/olsrd2.init
++++ b/oonf-olsrd2/files/olsrd2.init
+@@ -3,4 +3,11 @@
+ START=82
+ DAEMON='olsrd2'
+ 
++running() {
++  test -e "/tmp/run/olsrd2.pid" && test -e "/proc/$(cat "/tmp/run/olsrd2.pid")" && return 0
++  return 1
++}
++
++extra_command "running" "Check if service is running"
++
+ . /lib/functions/oonf_init.sh
diff --git a/targets/generic b/targets/generic
index c9bce1e0d87726b9255c633ad550da37ed4043c6..20111220c6a63bf1831c060111bbcb5351214fb2 100644
--- a/targets/generic
+++ b/targets/generic
@@ -68,6 +68,8 @@ config('KERNEL_SECCOMP', false)
 -- use try_config, so enabling the package is still possible
 try_config('PACKAGE_kmod-mt7915e', false)
 
+try_config('OONF_GENERIC_HTTP', true)
+
 config('COLLECT_KERNEL_DEBUG', true)
 
 config('TARGET_MULTI_PROFILE', true)