├── README.md ├── luci └── LuCI │ └── luci-app-netem │ ├── Makefile │ └── luasrc │ ├── controller │ └── netem.lua │ └── model │ └── cbi │ └── netem │ └── netem.lua └── net └── network └── netem-control ├── Makefile └── files ├── netem.config ├── netem.init └── sbin └── netem-control.lua /README.md: -------------------------------------------------------------------------------- 1 | openwrt-netem 2 | ============= 3 | 4 | OpenWrt packages for easy WAN emulation. 5 | 6 | ### Compiling the packages require the following steps: 7 | 1. Setup an OpenWrt buildroot, checkout the openwrt repo. Refer to https://openwrt.org/docs/guide-developer/toolchain/use-buildsystem 8 | 2. Copy these folders into the my_packages folder of the openwrt buildroot and update the feeds. 9 | ``` 10 | ./scripts/feeds update -a 11 | ./scripts/feeds install netem-control 12 | ./scripts/feeds install luci-app-netem 13 | ``` 14 | 3. Run `make menuconfig` and ensure both netem-control in Network and luci-app-netem in Luci > Applications is enabled 15 | 4. Compile both packages individually 16 | ``` 17 | make package/netem-control/{clean,compile} 18 | make package/luci-app-netem/{clean,compile} 19 | ``` 20 | 5. Resulting .ipk files will be placed in /bin/packages/x86_64/my_packages/ 21 | -------------------------------------------------------------------------------- /luci/LuCI/luci-app-netem/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2006 OpenWrt.org 3 | # Copyright (C) 2012 Connectify 4 | # 5 | # This is free software, licensed under the GNU General Public License v2. 6 | # See /LICENSE for more information. 7 | # 8 | include $(TOPDIR)/rules.mk 9 | 10 | PKG_NAME:=luci-app-netem 11 | PKG_RELEASE:=1 12 | PKG_VERSION:=0.1 13 | 14 | PKG_BUILD_DEPENDS:=$(if $(STAGING_DIR_ROOT),lua/host) 15 | PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) 16 | PKG_INSTALL_DIR:=$(PKG_BUILD_DIR)/ipkg-install 17 | 18 | include $(INCLUDE_DIR)/package.mk 19 | 20 | define Package/luci-app-netem 21 | SECTION:=luci 22 | CATEGORY:=LuCI 23 | SUBMENU:=3. Applications 24 | URL:=http://luci.freifunk-halle.net/ 25 | MAINTAINER:=Brian Prodoehl 26 | TITLE:=LuCI WAN Emulation Frontend 27 | DEPENDS:=+luci +netem-control 28 | endef 29 | 30 | define Build/Prepare 31 | mkdir -p $(PKG_BUILD_DIR) 32 | $(CP) * $(PKG_BUILD_DIR) 33 | endef 34 | 35 | define Build/Configure 36 | endef 37 | 38 | define Build/Compile 39 | endef 40 | 41 | define Package/luci-app-netem/install 42 | $(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller/ 43 | $(INSTALL_DATA) $(PKG_BUILD_DIR)/luasrc/controller/* $(1)/usr/lib/lua/luci/controller/ 44 | 45 | $(INSTALL_DIR) $(1)/usr/lib/lua/luci/model/cbi/netem/ 46 | $(INSTALL_DATA) $(PKG_BUILD_DIR)/luasrc/model/cbi/netem/* $(1)/usr/lib/lua/luci/model/cbi/netem/ 47 | endef 48 | 49 | $(eval $(call BuildPackage,luci-app-netem)) 50 | -------------------------------------------------------------------------------- /luci/LuCI/luci-app-netem/luasrc/controller/netem.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | LuCI - Lua Configuration Interface 3 | 4 | Copyright 2008 Steven Barth 5 | Copyright 2012 Connectify 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | ]]-- 13 | 14 | module("luci.controller.netem", package.seeall) 15 | 16 | function index() 17 | if not nixio.fs.access("/etc/config/netem") then 18 | return 19 | end 20 | 21 | local page 22 | 23 | page = entry({"admin", "network", "netem"}, cbi("netem/netem"), _("WAN Emulation")) 24 | page.i18n = "netem" 25 | page.dependent = true 26 | 27 | page = entry({"mini", "network", "netem"}, cbi("netem/netemmini", {autoapply=true}), _("WAN Emulation")) 28 | page.i18n = "netem" 29 | page.dependent = true 30 | end 31 | 32 | -------------------------------------------------------------------------------- /luci/LuCI/luci-app-netem/luasrc/model/cbi/netem/netem.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | LuCI - Lua Configuration Interface 3 | 4 | Copyright 2008 Steven Barth 5 | Copyright 2012 Connectify 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | ]]-- 13 | 14 | local wa = require "luci.tools.webadmin" 15 | local ut = require "luci.util" 16 | local nw = require "luci.model.network" 17 | local fs = require "nixio.fs" 18 | 19 | m = Map("netem", translate("WAN Emulation"), 20 | translate("With WAN Emulation you can simulate various WAN network conditions.")) 21 | 22 | s = m:section(TypedSection, "interface", translate("Interfaces")) 23 | s.addremove = true 24 | s.anonymous = false 25 | 26 | ifname = s:option(Value, "ifname", translate("Interface"), 27 | translate("These rules will apply to OUTGOING traffic on this physical interface.")) 28 | ifname.template = "cbi/network_ifacelist" 29 | ifname.widget = "radio" 30 | ifname.nobridges = false 31 | ifname.rmempty = false 32 | ifname.network = arg[1] 33 | 34 | e = s:option(Flag, "enabled", translate("Enable"), translate("Enable/Disable WAN Emulation on this interface.")) 35 | e.rmempty = false 36 | 37 | s:option(Flag, "delay", translate("Packet Delay"), translate("Add a delay to all outgoing packets.")) 38 | delay_ms = s:option(Value, "delay_ms", translate("Delay (ms)"), translate("The base amount of fixed delay to be added to every ougoing packet.")) 39 | delay_ms:depends({delay="1"}) 40 | delay_ms.datatype = "uinteger" 41 | delay_var = s:option(Value, "delay_var", translate("Delay +/- Variation (ms)"), translate("The amount of variation(jitter) that can be added or removed from the base fixed delay.")) 42 | delay_var:depends({delay="1"}) 43 | delay_var.datatype = "uinteger" 44 | delay_corr = s:option(Value, "delay_corr", translate("Delay Correlation (%)"), translate("Probability that the variation will be the same as that of the previous packet.")) 45 | delay_corr:depends({delay="1"}) 46 | delay_corr.datatype = "range(0,100)" 47 | 48 | reordering = s:option(Flag, "reordering", translate("Packet Re-ordering"), translate("If the inter-packet gap is less than the specified Delay (ms), then packets can be reordered.")) 49 | reordering:depends({delay="1"}) 50 | reordering_immed_pct = s:option(Value, "reordering_immed_pct", translate("Immediate Delivery (%)"), translate("Probability that a packet will be delivered immediately.")) 51 | reordering_immed_pct:depends({reordering="1"}) 52 | reordering_immed_pct.datatype = "range(0,100)" 53 | reordering_corr = s:option(Value, "reordering_corr", translate("Reordering Correlation (%)"), translate("Probability that a packet will be delayed the same as the previous packet.")) 54 | reordering_corr:depends({reordering="1"}) 55 | reordering_corr.datatype = "range(0,100)" 56 | 57 | s:option(Flag, "loss", translate("Packet Loss"), translate("Packets can be randomly dropped.")) 58 | loss_pct = s:option(Value, "loss_pct", translate("Loss (%)"), translate("Probability that a packet will be dropped.")) 59 | loss_pct:depends({loss="1"}) 60 | loss_pct.datatype = "range(0,100)" 61 | loss_corr = s:option(Value, "loss_corr", translate("Loss Correlation (%)"), translate("Probability that a packet will do the same thing as the previous packet. Bursts of packet loss can be achieved with this parameter.")) 62 | loss_corr:depends({loss="1"}) 63 | loss_corr.datatype = "range(0,100)" 64 | 65 | s:option(Flag, "duplication", translate("Packet Duplication"), translate("Packets can be randomly duplicated.")) 66 | duplication_pct = s:option(Value, "duplication_pct", translate("Duplication (%)"), translate("Probability that a packet will be duplicated.")) 67 | duplication_pct:depends({duplication="1"}) 68 | duplication_pct.datatype = "range(0,100)" 69 | 70 | s:option(Flag, "corruption", translate("Packet Corruption"), translate("Packets can be randomly corrupted with single-bit errors.")) 71 | corruption_pct = s:option(Value, "corruption_pct", translate("Corruption (%)"), translate("Probability that a packet will be corrupted.")) 72 | corruption_pct:depends({corruption="1"}) 73 | corruption_pct.datatype = "range(0,100)" 74 | 75 | s:option(Flag, "ratecontrol", translate("Rate Control"), translate("OUTGOING traffic can be rate-limited.")) 76 | ratecontrol_rate = s:option(Value, "ratecontrol_rate", translate("Download Speed (kbit/s)")) 77 | ratecontrol_rate:depends({ratecontrol="1"}) 78 | ratecontrol_rate.datatype = "uinteger" 79 | 80 | return m 81 | -------------------------------------------------------------------------------- /net/network/netem-control/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2006 OpenWrt.org 3 | # Copyright (C) 2012 Connectify 4 | # 5 | # This is free software, licensed under the GNU General Public License v2. 6 | # See /LICENSE for more information. 7 | # 8 | include $(TOPDIR)/rules.mk 9 | 10 | PKG_NAME:=netem-control 11 | PKG_VERSION:=0.2 12 | PKG_RELEASE:=1 13 | PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) 14 | 15 | PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) 16 | PKG_INSTALL_DIR:=$(PKG_BUILD_DIR)/ipkg-install 17 | 18 | include $(INCLUDE_DIR)/package.mk 19 | 20 | define Package/netem-control 21 | SECTION:=net 22 | CATEGORY:=Network 23 | DEPENDS:=+kmod-netem +kmod-sched +lua +tc +luaposix +libuci-lua +luci-compat 24 | TITLE:=WAN emulation with netem. 25 | endef 26 | 27 | define Build/Compile 28 | endef 29 | 30 | define Package/netem-control/conffiles 31 | /etc/config/netem 32 | endef 33 | 34 | define Package/netem-control/install 35 | $(INSTALL_DIR) $(1)/sbin 36 | $(INSTALL_BIN) ./files/sbin/netem-control.lua $(1)/sbin/netem-control 37 | 38 | $(INSTALL_DIR) $(1)/etc/config 39 | $(INSTALL_DATA) ./files/netem.config $(1)/etc/config/netem 40 | 41 | $(INSTALL_DIR) $(1)/etc/init.d 42 | $(INSTALL_BIN) ./files/netem.init $(1)/etc/init.d/netem-control 43 | endef 44 | 45 | define Package/netem-control/postinst 46 | #!/bin/sh 47 | 48 | # do not change below 49 | # check if we are on real system 50 | if [ -z "$${IPKG_INSTROOT}" ]; then 51 | /etc/init.d/netem-control enable 52 | fi 53 | endef 54 | 55 | define Package/netem-control/prerm 56 | #!/bin/sh 57 | # check if we are on real system 58 | if [ -z "$${IPKG_INSTROOT}" ]; then 59 | /etc/init.d/netem-control disable 60 | fi 61 | exit 0 62 | endef 63 | 64 | $(eval $(call BuildPackage,netem-control)) 65 | -------------------------------------------------------------------------------- /net/network/netem-control/files/netem.config: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /net/network/netem-control/files/netem.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | USE_PROCD=1 3 | START=98 4 | STOP=01 5 | 6 | start_instance() { 7 | local cfg="$1" 8 | cfgnum = cfgnum + 1 9 | #echo $cfg 10 | local enabled 11 | local ifname 12 | 13 | config_get_bool enabled $cfg enabled 14 | #echo $enabled 15 | 16 | if [ $enabled == "0" ]; then 17 | echo "Netem rule: $cfg disabled in config" 18 | return 1 19 | fi 20 | # Reading config 21 | 22 | config_get ifname $cfg ifname 23 | 24 | procd_open_instance 25 | procd_set_param command /usr/bin/lua "/sbin/netem-control" 26 | procd_set_param stdout 1 27 | procd_set_param stderr 1 28 | procd_close_instance 29 | } 30 | 31 | start_service() { 32 | #config_load 'netem' 33 | #config_foreach start_instance interface 34 | procd_open_instance 35 | procd_set_param command /usr/bin/lua "/sbin/netem-control" 36 | procd_set_param stdout 1 37 | procd_set_param stderr 1 38 | procd_close_instance 39 | } 40 | 41 | reload_service() 42 | { 43 | stop 44 | start 45 | } 46 | 47 | service_triggers() { 48 | procd_add_reload_trigger 'netem' 49 | } -------------------------------------------------------------------------------- /net/network/netem-control/files/sbin/netem-control.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | --[[ 3 | Copyright 2012 Connectify 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | --]] 11 | 12 | require("uci") 13 | local signal = require("posix.signal") 14 | intfcfg = {} 15 | 16 | function exec (cmd) 17 | print (cmd) 18 | return os.execute(cmd) 19 | end 20 | 21 | function setRules (operation) 22 | local x = uci.cursor() 23 | local qdisc_parent = 0 24 | local errors = 0 25 | 26 | if ( operation == "del") then 27 | --remove all rules associated to previously added interfaces 28 | for k,v in ipairs(intfcfg) do 29 | tc_str = "tc qdisc del dev "..v.." root" 30 | exec(tc_str) 31 | end 32 | table.remove(intfcfg) 33 | return 34 | end 35 | 36 | x:foreach("netem", "interface", 37 | function (section) 38 | 39 | iface = x:get("netem", section[".name"], "ifname") 40 | if iface == nil then 41 | return 42 | end 43 | 44 | enabled = x:get("netem", section[".name"], "enabled") 45 | if enabled == "1" then 46 | qdisc_parent = qdisc_parent + 1 47 | --keep track of all interfaces where we are adding rules 48 | table.insert(intfcfg, iface) 49 | 50 | if (operation == "add") then 51 | --tc_str = "tc qdisc del dev "..iface.." root" 52 | --exec(tc_str) 53 | tc_str = "tc qdisc replace dev "..iface.." handle "..qdisc_parent.. 54 | ": root htb default 11" 55 | exec(tc_str) 56 | end 57 | 58 | tc_str = "tc class "..operation.." dev "..iface.." parent ".. 59 | qdisc_parent..": classid "..qdisc_parent..":1 htb rate 1000Mbit quantum 60000" 60 | err = exec(tc_str); 61 | if err ~= 0 then 62 | errors = errors + 1 63 | return err 64 | end 65 | 66 | ratelimit = "1000Mbit" 67 | rate_control_enabled = x:get("netem", section[".name"], "ratecontrol") 68 | if rate_control_enabled == "1" then 69 | ratelimit = x:get("netem", section[".name"], "ratecontrol_rate") 70 | if ratelimit == nil then 71 | ratelimit = 1000000 72 | end 73 | ratelimit = ratelimit.."kbit" 74 | end 75 | 76 | tc_str = "tc class "..operation.." dev "..iface.." parent "..qdisc_parent.. 77 | ":1 classid "..qdisc_parent..":11 htb rate "..ratelimit.." quantum 60000" 78 | err = exec(tc_str); 79 | if err ~= 0 then 80 | errors = errors + 1 81 | return err 82 | end 83 | 84 | last_id = 11 85 | parent = last_id 86 | current_id = last_id + 1 87 | netem_used = 0 88 | 89 | tc_str = "tc qdisc "..operation.." dev "..iface.." parent "..qdisc_parent.. 90 | ":"..parent.." handle "..current_id..": netem" 91 | last_id = current_id 92 | 93 | delay_enabled = x:get("netem", section[".name"], "delay") 94 | if delay_enabled == "1" then 95 | netem_used = 1 96 | delay_ms = x:get("netem", section[".name"], "delay_ms") 97 | if delay_ms == nil then 98 | delay_ms = 0 99 | end 100 | delay_var = x:get("netem", section[".name"], "delay_var") 101 | if delay_var == nil then 102 | delay_var = 0 103 | end 104 | delay_corr = x:get("netem", section[".name"], "delay_corr") 105 | if delay_corr == nil then 106 | delay_corr = 0 107 | end 108 | tc_str = tc_str.." delay "..delay_ms.."ms "..delay_var.."ms "..delay_corr.."%" 109 | end 110 | 111 | reorder_enabled = x:get("netem", section[".name"], "reordering") 112 | if reorder_enabled == "1" then 113 | netem_used = 1 114 | reorder_pct = x:get("netem", section[".name"], "reordering_immed_pct") 115 | if reorder_pct == nil then 116 | reorder_pct = 0 117 | end 118 | reorder_corr = x:get("netem", section[".name"], "reordering_corr") 119 | if reorder_corr == nil then 120 | reorder_corr = 0 121 | end 122 | tc_str = tc_str.." reorder "..reorder_pct.."% "..reorder_corr.."%" 123 | end 124 | 125 | loss_enabled = x:get("netem", section[".name"], "loss") 126 | if loss_enabled == "1" then 127 | netem_used = 1 128 | loss_pct = x:get("netem", section[".name"], "loss_pct") 129 | if loss_pct == nil then 130 | loss_pct = 0 131 | end 132 | loss_corr = x:get("netem", section[".name"], "loss_corr") 133 | if loss_corr == nil then 134 | loss_corr = 0 135 | end 136 | tc_str = tc_str.." loss "..loss_pct.."% "..loss_corr.."%" 137 | end 138 | 139 | dupe_enabled = x:get("netem", section[".name"], "duplication") 140 | if dupe_enabled == "1" then 141 | netem_used = 1 142 | dupe_pct = x:get("netem", section[".name"], "duplication_pct") 143 | if dupe_pct == nil then 144 | dupe_pct = 0 145 | end 146 | tc_str = tc_str.." duplicate "..dupe_pct.."%" 147 | end 148 | 149 | corrupt_enabled = x:get("netem", section[".name"], "corruption") 150 | if corrupt_enabled == "1" then 151 | netem_used = 1 152 | corrupt_pct = x:get("netem", section[".name"], "corruption_pct") 153 | if corrupt_pct == nil then 154 | corrupt_pct = 0 155 | end 156 | tc_str = tc_str.." corrupt "..corrupt_pct.."%" 157 | end 158 | 159 | if netem_used == 0 then 160 | tc_str = tc_str.." delay 0ms" 161 | end 162 | 163 | err = exec(tc_str) 164 | if err ~= 0 then 165 | errors = errors + 1 166 | return err 167 | end 168 | return 0 169 | else 170 | -- blow away the root in case this was enabled, and isn't anymore 171 | --tc_str = "tc qdisc del dev "..iface.." root" 172 | --exec(tc_str) 173 | end 174 | end) 175 | return errors 176 | end 177 | 178 | 179 | 180 | signal.signal(signal.SIGTERM, function(signum) 181 | io.write("\n") 182 | print ("Unloading WAN Emulation rules...") 183 | setRules("del") 184 | 185 | os.exit(128 + signum) 186 | end) 187 | 188 | print("Loading WAN Emulation rules...") 189 | setRules("add") 190 | 191 | --endless loop to keep the service running 192 | 193 | while( true ) 194 | do 195 | -- use os sleep to wait and block with low cpu 196 | os.execute("sleep 1") 197 | 198 | end 199 | 200 | return --------------------------------------------------------------------------------