Signed-off-by: Jianhui Zhao <jianhuizhao329@gmail.com>lilik-openwrt-22.03
@ -0,0 +1,37 @@ | |||||
#!/usr/bin/env lua | |||||
--[[ | |||||
Copyright (C) 2018 Jianhui Zhao <jianhuizhao329@gmail.com> | |||||
This program is free software; you can redistribute it and/or | |||||
modify it under the terms of the GNU Lesser General Public | |||||
License as published by the Free Software Foundation; either | |||||
version 2.1 of the License, or (at your option) any later version. | |||||
This program is distributed in the hope that it will be useful, | |||||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
Lesser General Public License for more details. | |||||
You should have received a copy of the GNU Lesser General Public | |||||
License along with this library; if not, write to the Free Software | |||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 | |||||
USA | |||||
--]] | |||||
local uloop = require "uloop" | |||||
local uh = require "uhttpd" | |||||
local auth = require "wifidog-ng.auth" | |||||
local ubus = require "wifidog-ng.ubus" | |||||
local version = require "wifidog-ng.version" | |||||
local heartbeat = require "wifidog-ng.heartbeat" | |||||
uh.log(uh.LOG_INFO, "Version: " .. version.string()) | |||||
uloop.init() | |||||
ubus.init() | |||||
auth.init() | |||||
heartbeat.start() | |||||
uloop.run() |
@ -0,0 +1,221 @@ | |||||
--[[ | |||||
Copyright (C) 2018 Jianhui Zhao <jianhuizhao329@gmail.com> | |||||
This program is free software; you can redistribute it and/or | |||||
modify it under the terms of the GNU Lesser General Public | |||||
License as published by the Free Software Foundation; either | |||||
version 2.1 of the License, or (at your option) any later version. | |||||
This program is distributed in the hope that it will be useful, | |||||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
Lesser General Public License for more details. | |||||
You should have received a copy of the GNU Lesser General Public | |||||
License along with this library; if not, write to the Free Software | |||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 | |||||
USA | |||||
--]] | |||||
local uh = require "uhttpd" | |||||
local http = require "socket.http" | |||||
local util = require "wifidog-ng.util" | |||||
local config = require "wifidog-ng.config" | |||||
local M = {} | |||||
local apple_host = { | |||||
["captive.apple.com"] = true, | |||||
["www.apple.com"] = true, | |||||
} | |||||
local terms = {} | |||||
local function is_authed_user(mac) | |||||
local r = os.execute("ipset test wifidog-ng-mac " .. mac .. " 2> /dev/null") | |||||
return r == 0 | |||||
end | |||||
local function allow_user(mac, temppass) | |||||
if not temppass then | |||||
terms[mac].authed = true | |||||
os.execute("ipset add wifidog-ng-mac " .. mac) | |||||
else | |||||
local cfg = config.get() | |||||
os.execute("ipset add wifidog-ng-mac " .. mac .. " timeout " .. cfg.temppass_time) | |||||
end | |||||
end | |||||
local function deny_user(mac) | |||||
os.execute("ipset del wifidog-ng-mac " .. mac) | |||||
end | |||||
function M.get_terms() | |||||
local r = {} | |||||
for k, v in pairs(terms) do | |||||
if v.authed then | |||||
r[k] = {ip = v.ip} | |||||
end | |||||
end | |||||
return r | |||||
end | |||||
function M.new_term(ip, mac, token) | |||||
terms[mac] = {ip = ip, token = token} | |||||
if token then | |||||
terms[mac].authed = true | |||||
allow_user(mac) | |||||
end | |||||
end | |||||
local function http_callback_auth(cl) | |||||
local cfg = config.get() | |||||
local token = cl:get_var("token") | |||||
local ip = cl:get_remote_addr() | |||||
local mac = util.arp_get(cfg.gw_ifname, ip) | |||||
if not mac then | |||||
uh.log(uh.LOG_ERR, "Not found macaddr for " .. ip) | |||||
cl:send_error(401, "Unauthorized", "Not found your macaddr") | |||||
return uh.REQUEST_DONE | |||||
end | |||||
if token and #token > 0 then | |||||
if cl:get_var("logout") then | |||||
local url = string.format("%s&stage=logout&ip=%s&mac=%s&token=%s", cfg.auth_url, ip, mac, token) | |||||
http.request(url) | |||||
deny_user(mac) | |||||
else | |||||
local url = string.format("%s&stage=login&ip=%s&mac=%s&token=%s", cfg.auth_url, ip, mac, token) | |||||
local r = http.request(url) | |||||
if not r then | |||||
cl:send_error(401, "Unauthorized") | |||||
return uh.REQUEST_DONE | |||||
end | |||||
local auth = r:match("Auth: (%d)") | |||||
if auth == "1" then | |||||
allow_user(mac) | |||||
cl:redirect(302, string.format("%s&mac=%s", cfg.portal_url, mac)) | |||||
else | |||||
cl:redirect(302, string.format("%s&mac=%s", cfg.msg_url, mac)) | |||||
return uh.REQUEST_DONE | |||||
end | |||||
end | |||||
else | |||||
cl:send_error(401, "Unauthorized") | |||||
return uh.REQUEST_DONE | |||||
end | |||||
end | |||||
local function http_callback_temppass(cl) | |||||
local cfg = config.get() | |||||
local ip = cl:get_remote_addr() | |||||
local mac = util.arp_get(cfg.gw_ifname, ip) | |||||
if not mac then | |||||
uh.log(uh.LOG_ERR, "Not found macaddr for " .. ip) | |||||
cl:send_error(401, "Unauthorized", "Not found your macaddr") | |||||
return uh.REQUEST_DONE | |||||
end | |||||
local script = cl:get_var("script") or "" | |||||
cl:send_header(200, "OK", -1) | |||||
cl:header_end() | |||||
allow_user(mac, true) | |||||
cl:chunk_send(cl:get_var("script") or ""); | |||||
cl:request_done() | |||||
return uh.REQUEST_DONE | |||||
end | |||||
local function http_callback_404(cl, path) | |||||
local cfg = config.get() | |||||
if cl:get_http_method() ~= uh.HTTP_METHOD_GET then | |||||
cl:send_error(401, "Unauthorized") | |||||
return uh.REQUEST_DONE | |||||
end | |||||
local ip = cl:get_remote_addr() | |||||
local mac = util.arp_get(cfg.gw_ifname, ip) | |||||
if not mac then | |||||
uh.log(uh.LOG_ERR, "Not found macaddr for " .. ip) | |||||
cl:send_error(401, "Unauthorized", "Not found your macaddr") | |||||
return uh.REQUEST_DONE | |||||
end | |||||
term = terms[mac] | |||||
if not term then | |||||
terms[mac] = {ip = ip} | |||||
end | |||||
term = terms[mac] | |||||
if is_authed_user(mac) then | |||||
cl:redirect(302, "%s&mac=%s", cfg.portal_url, mac) | |||||
return uh.REQUEST_DONE | |||||
end | |||||
cl:send_header(200, "OK", -1) | |||||
cl:header_end() | |||||
local header_host = cl:get_header("host") | |||||
if apple_host[header_host] then | |||||
local http_ver = cl:get_http_version() | |||||
if http_ver == uh.HTTP_VER_10 then | |||||
if not term.apple then | |||||
cl:chunk_send("fuck you") | |||||
term.apple = true | |||||
cl:request_done() | |||||
return uh.REQUEST_DONE | |||||
end | |||||
end | |||||
end | |||||
local redirect_html = [[ | |||||
<!doctype html><html><head><title>Success</title> | |||||
<script type="text/javascript"> | |||||
setTimeout(function() {location.replace('%s&ip=%s&mac=%s');}, 1);</script> | |||||
<style type="text/css">body {color:#FFF}</style></head> | |||||
<body>Success</body></html> | |||||
]] | |||||
cl:chunk_send(string.format(redirect_html, cfg.login_url, ip, mac)) | |||||
cl:request_done() | |||||
return uh.REQUEST_DONE | |||||
end | |||||
local function on_request(cl, path) | |||||
if path == "/wifidog/auth" then | |||||
return http_callback_auth(cl) | |||||
elseif path == "/wifidog/temppass" then | |||||
return http_callback_temppass(cl) | |||||
end | |||||
return uh.REQUEST_CONTINUE | |||||
end | |||||
function M.init() | |||||
local cfg = config.get() | |||||
local srv = uh.new(cfg.gw_address, cfg.gw_port) | |||||
srv:on_request(on_request) | |||||
srv:on_error404(http_callback_404) | |||||
if uh.SSL_SUPPORTED then | |||||
local srv_ssl = uh.new(cfg.gw_address, cfg.gw_ssl_port) | |||||
srv_ssl:ssl_init("/etc/wifidog-ng/ssl.crt", "/etc/wifidog-ng/ssl.key") | |||||
srv_ssl:on_request(on_request) | |||||
srv_ssl:on_error404(http_callback_404) | |||||
end | |||||
end | |||||
return M |
@ -0,0 +1,158 @@ | |||||
--[[ | |||||
Copyright (C) 2018 Jianhui Zhao <jianhuizhao329@gmail.com> | |||||
This program is free software; you can redistribute it and/or | |||||
modify it under the terms of the GNU Lesser General Public | |||||
License as published by the Free Software Foundation; either | |||||
version 2.1 of the License, or (at your option) any later version. | |||||
This program is distributed in the hope that it will be useful, | |||||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
Lesser General Public License for more details. | |||||
You should have received a copy of the GNU Lesser General Public | |||||
License along with this library; if not, write to the Free Software | |||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 | |||||
USA | |||||
--]] | |||||
local uci = require "uci" | |||||
local util = require "wifidog-ng.util" | |||||
local M = {} | |||||
local cfg = {} | |||||
function M.parse() | |||||
local c = uci.cursor() | |||||
c:foreach('wifidog-ng', 'gateway', function(s) | |||||
local port = s.port or 2060 | |||||
local ssl_port = s.ssl_port or 8443 | |||||
local interface = s.interface or "lan" | |||||
local checkinterval = s.checkinterval or 30 | |||||
local client_timeout = s.client_timeout or 5 | |||||
local temppass_time = s.temppass_time or 30 | |||||
local id = s.id | |||||
local address = s.address | |||||
cfg.gw_port = tonumber(port) | |||||
cfg.gw_ssl_port = tonumber(ssl_port) | |||||
cfg.checkinterval = tonumber(checkinterval) | |||||
cfg.client_timeout = tonumber(client_timeout) | |||||
cfg.temppass_time = tonumber(temppass_time) | |||||
cfg.gw_address = s.address | |||||
cfg.gw_id = s.id | |||||
local st = util.ubus("network.interface." .. interface, "status") | |||||
cfg.gw_ifname = st.device | |||||
if not cfg.gw_address then | |||||
cfg.gw_address = st["ipv4-address"][1].address | |||||
end | |||||
if not cfg.gw_id then | |||||
local devst = util.ubus("network.device", "status", {name = st.device}) | |||||
local macaddr = devst.macaddr | |||||
cfg.gw_id = macaddr:gsub(":", ""):upper() | |||||
end | |||||
end) | |||||
c:foreach('wifidog-ng', 'server', function(s) | |||||
local host = s.host | |||||
local path = s.path or "/wifidog/" | |||||
local gw_port = cfg.gw_port | |||||
local gw_id = cfg.gw_id | |||||
local gw_address = cfg.gw_address | |||||
local ssid = cfg.ssid or "" | |||||
local proto, port = "http", "" | |||||
if s.port ~= "80" and s.port ~= "443" then | |||||
port = ":" .. s.port | |||||
end | |||||
if s.ssl == "1" then | |||||
proto = "https" | |||||
end | |||||
cfg.login_url = string.format("%s://%s%s%s%s?gw_address=%s&gw_port=%d&gw_id=%s&ssid=%s", | |||||
proto, host, port, path, s.login_path, gw_address, gw_port, gw_id, ssid) | |||||
cfg.auth_url = string.format("%s://%s%s%s%s?gw_id=%s", | |||||
proto, host, port, path, s.auth_path, gw_id) | |||||
cfg.ping_url = string.format("%s://%s%s%s%s?gw_id=%s", | |||||
proto, host, port, path, s.ping_path, gw_id) | |||||
cfg.portal_url = string.format("%s://%s%s%s%s?gw_id=%s", | |||||
proto, host, port, path, s.portal_path, gw_id) | |||||
cfg.msg_url = string.format("%s://%s%s%s%s?gw_id=%s", | |||||
proto, host, port, path, s.msg_path, gw_id) | |||||
end) | |||||
cfg.parsed = true | |||||
end | |||||
function M.get() | |||||
if not cfg.parsed then | |||||
M.parse() | |||||
end | |||||
return cfg | |||||
end | |||||
function M.add_whitelist(typ, value) | |||||
local c = uci.cursor() | |||||
local opt | |||||
if typ == "mac" then | |||||
typ = "validated_user" | |||||
opt = "mac" | |||||
elseif typ == "domain" then | |||||
typ = "validated_domain" | |||||
opt = "domain" | |||||
else | |||||
return | |||||
end | |||||
local exist = false | |||||
c:foreach("wifidog-ng", typ, function(s) | |||||
if s[opt] == value then | |||||
exist = true | |||||
end | |||||
end) | |||||
if not exist then | |||||
local s = c:add("wifidog-ng", typ) | |||||
c:set("wifidog-ng", s, opt, value) | |||||
c:commit("wifidog-ng") | |||||
end | |||||
end | |||||
function M.del_whitelist(typ, value) | |||||
local c = uci.cursor() | |||||
local opt | |||||
if typ == "mac" then | |||||
typ = "validated_user" | |||||
opt = "mac" | |||||
elseif typ == "domain" then | |||||
typ = "validated_domain" | |||||
opt = "domain" | |||||
else | |||||
return | |||||
end | |||||
c:foreach("wifidog-ng", typ, function(s) | |||||
if s[opt] == value then | |||||
c:delete("wifidog-ng", s[".name"]) | |||||
end | |||||
end) | |||||
c:commit("wifidog-ng") | |||||
end | |||||
return M |
@ -0,0 +1,46 @@ | |||||
--[[ | |||||
Copyright (C) 2018 Jianhui Zhao <jianhuizhao329@gmail.com> | |||||
This program is free software; you can redistribute it and/or | |||||
modify it under the terms of the GNU Lesser General Public | |||||
License as published by the Free Software Foundation; either | |||||
version 2.1 of the License, or (at your option) any later version. | |||||
This program is distributed in the hope that it will be useful, | |||||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
Lesser General Public License for more details. | |||||
You should have received a copy of the GNU Lesser General Public | |||||
License along with this library; if not, write to the Free Software | |||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 | |||||
USA | |||||
--]] | |||||
local uloop = require "uloop" | |||||
local http = require "socket.http" | |||||
local util = require "wifidog-ng.util" | |||||
local config = require "wifidog-ng.config" | |||||
local M = {} | |||||
local timer = nil | |||||
local start_time = os.time() | |||||
local function heartbeat() | |||||
local cfg = config.get() | |||||
timer:set(1000 * cfg.checkinterval) | |||||
local sysinfo = util.ubus("system", "info") | |||||
local url = string.format("%s&sys_uptime=%d&sys_memfree=%d&sys_load=%d&wifidog_uptime=%d", | |||||
cfg.ping_url, sysinfo.uptime, sysinfo.memory.free, sysinfo.load[1], os.time() - start_time) | |||||
http.request(url) | |||||
end | |||||
function M.start() | |||||
timer = uloop.timer(heartbeat, 1000) | |||||
end | |||||
return M |
@ -0,0 +1,128 @@ | |||||
--[[ | |||||
Copyright (C) 2018 Jianhui Zhao <jianhuizhao329@gmail.com> | |||||
This program is free software; you can redistribute it and/or | |||||
modify it under the terms of the GNU Lesser General Public | |||||
License as published by the Free Software Foundation; either | |||||
version 2.1 of the License, or (at your option) any later version. | |||||
This program is distributed in the hope that it will be useful, | |||||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
Lesser General Public License for more details. | |||||
You should have received a copy of the GNU Lesser General Public | |||||
License along with this library; if not, write to the Free Software | |||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 | |||||
USA | |||||
--]] | |||||
local uci = require "uci" | |||||
local ubus = require "ubus" | |||||
local http = require "socket.http" | |||||
local auth = require "wifidog-ng.auth" | |||||
local config = require "wifidog-ng.config" | |||||
local M = {} | |||||
local conn = nil | |||||
local ubus_codes = { | |||||
["INVALID_COMMAND"] = 1, | |||||
["INVALID_ARGUMENT"] = 2, | |||||
["METHOD_NOT_FOUND"] = 3, | |||||
["NOT_FOUND"] = 4, | |||||
["NO_DATA"] = 5, | |||||
["PERMISSION_DENIED"] = 6, | |||||
["TIMEOUT"] = 7, | |||||
["NOT_SUPPORTED"] = 8, | |||||
["UNKNOWN_ERROR"] = 9, | |||||
["CONNECTION_FAILED"] = 10 | |||||
} | |||||
local function reload_validated_domain() | |||||
local c = uci.cursor() | |||||
local file = io.open("/tmp/dnsmasq.d/wifidog-ng", "w") | |||||
c:foreach("wifidog-ng", "validated_domain", function(s) | |||||
file:write("ipset=/" .. s.domain .. "/wifidog-ng-ip\n") | |||||
end) | |||||
file:close() | |||||
os.execute("/etc/init.d/dnsmasq restart &") | |||||
end | |||||
local methods = { | |||||
["wifidog-ng"] = { | |||||
roam = { | |||||
function(req, msg) | |||||
local cfg = config.get() | |||||
if not msg.ip or not msg.mac then | |||||
return ubus_codes["INVALID_ARGUMENT"] | |||||
end | |||||
local url = string.format("%s&stage=roam&ip=%s&mac=%s", cfg.auth_url, msg.ip, msg.mac) | |||||
local r = http.request(url) or "" | |||||
local token = r:match("token=(%w+)") | |||||
if token then | |||||
auth.new_term(msg.ip, msg.mac, token) | |||||
end | |||||
end, {ip = ubus.STRING, mac = ubus.STRING } | |||||
}, | |||||
term = { | |||||
function(req, msg) | |||||
if msg.action == "show" then | |||||
conn:reply(req, {terms = auth.get_terms()}); | |||||
return | |||||
end | |||||
if not msg.action or not msg.mac then | |||||
return ubus_codes["INVALID_ARGUMENT"] | |||||
end | |||||
if msg.action == "add" then | |||||
auth.allow_user(mac) | |||||
elseif msg.action == "del" then | |||||
auth.deny_user(mac) | |||||
end | |||||
end, {action = ubus.STRING, mac = ubus.STRING } | |||||
}, | |||||
whitelist = { | |||||
function(req, msg) | |||||
if not msg.action or not msg.type or not msg.value then | |||||
return ubus_codes["INVALID_ARGUMENT"] | |||||
end | |||||
if msg.action == "add" then | |||||
config.add_whitelist(msg.type, msg.value) | |||||
if msg.type == "mac" then | |||||
auth.allow_user(msg.value) | |||||
end | |||||
elseif msg.action == "del" then | |||||
config.del_whitelist(msg.type, msg.value) | |||||
if msg.type == "mac" then | |||||
auth.deny_user(msg.value) | |||||
end | |||||
end | |||||
if msg.type == "domain" then | |||||
reload_validated_domain() | |||||
end | |||||
end, {action = ubus.STRING, type = ubus.STRING, value = ubus.STRING } | |||||
}, | |||||
} | |||||
} | |||||
function M.init() | |||||
conn = ubus.connect() | |||||
if not conn then | |||||
error("Failed to connect to ubus") | |||||
end | |||||
conn:add(methods) | |||||
end | |||||
return M |
@ -0,0 +1,83 @@ | |||||
--[[ | |||||
Copyright (C) 2018 Jianhui Zhao <jianhuizhao329@gmail.com> | |||||
This program is free software; you can redistribute it and/or | |||||
modify it under the terms of the GNU Lesser General Public | |||||
License as published by the Free Software Foundation; either | |||||
version 2.1 of the License, or (at your option) any later version. | |||||
This program is distributed in the hope that it will be useful, | |||||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
Lesser General Public License for more details. | |||||
You should have received a copy of the GNU Lesser General Public | |||||
License along with this library; if not, write to the Free Software | |||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 | |||||
USA | |||||
--]] | |||||
local _ubus = require "ubus" | |||||
local _ubus_connection = nil | |||||
local M = {} | |||||
function M.arp_get(ifname, ipaddr) | |||||
for l in io.lines("/proc/net/arp") do | |||||
local f = {} | |||||
for e in string.gmatch(l, "%S+") do | |||||
f[#f + 1] = e | |||||
end | |||||
if f[1] == ipaddr and f[6] == ifname then | |||||
return f[4] | |||||
end | |||||
end | |||||
end | |||||
function M.read_file(path, len) | |||||
local file = io.open(path, "r") | |||||
if not file then return nil end | |||||
if not len then len = "*a" end | |||||
local data = file:read(len) | |||||
file:close() | |||||
return data | |||||
end | |||||
local ubus_codes = { | |||||
"INVALID_COMMAND", | |||||
"INVALID_ARGUMENT", | |||||
"METHOD_NOT_FOUND", | |||||
"NOT_FOUND", | |||||
"NO_DATA", | |||||
"PERMISSION_DENIED", | |||||
"TIMEOUT", | |||||
"NOT_SUPPORTED", | |||||
"UNKNOWN_ERROR", | |||||
"CONNECTION_FAILED" | |||||
} | |||||
function M.ubus(object, method, data) | |||||
if not _ubus_connection then | |||||
_ubus_connection = _ubus.connect() | |||||
assert(_ubus_connection, "Unable to establish ubus connection") | |||||
end | |||||
if object and method then | |||||
if type(data) ~= "table" then | |||||
data = { } | |||||
end | |||||
local rv, err = _ubus_connection:call(object, method, data) | |||||
return rv, err, ubus_codes[err] | |||||
elseif object then | |||||
return _ubus_connection:signatures(object) | |||||
else | |||||
return _ubus_connection:objects() | |||||
end | |||||
end | |||||
return M |
@ -0,0 +1,30 @@ | |||||
--[[ | |||||
Copyright (C) 2018 Jianhui Zhao <jianhuizhao329@gmail.com> | |||||
This program is free software; you can redistribute it and/or | |||||
modify it under the terms of the GNU Lesser General Public | |||||
License as published by the Free Software Foundation; either | |||||
version 2.1 of the License, or (at your option) any later version. | |||||
This program is distributed in the hope that it will be useful, | |||||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
Lesser General Public License for more details. | |||||
You should have received a copy of the GNU Lesser General Public | |||||
License along with this library; if not, write to the Free Software | |||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 | |||||
USA | |||||
--]] | |||||
local M = { | |||||
MAJOR = 2, | |||||
MINOR = 0, | |||||
PATCH = 0 | |||||
} | |||||
function M.string() | |||||
return string.format("%d.%d.%d", M.MAJOR, M.MINOR, M.PATCH) | |||||
end | |||||
return M |
@ -0,0 +1,18 @@ | |||||
ifeq ($(findstring openwrt, $(CC)),) | |||||
ifneq ($(KERNELRELEASE),) | |||||
wifidog-ng-objs := main.o config.o | |||||
obj-m := wifidog-ng.o | |||||
else | |||||
KDIR = /lib/modules/$(shell uname -r)/build | |||||
all: | |||||
make -C $(KDIR) M=$(PWD) modules | |||||
clean: | |||||
rm -rf *.o *.ko *.mod.c Module.* modules.* .*.cmd .tmp* | |||||
endif | |||||
else | |||||
wifidog-ng-objs := main.o config.o | |||||
obj-m := wifidog-ng.o | |||||
endif |
@ -0,0 +1,174 @@ | |||||
/* | |||||
* Copyright (C) 2017 jianhui zhao <jianhuizhao329@gmail.com> | |||||
* | |||||
* This program is free software; you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License version 2 as | |||||
* published by the Free Software Foundation. | |||||
*/ | |||||
#include <linux/uaccess.h> | |||||
#include <linux/inetdevice.h> | |||||
#include <linux/seq_file.h> | |||||
#include "config.h" | |||||
static struct proc_dir_entry *proc; | |||||
static struct config conf; | |||||
static int update_gw_interface(const char *interface) | |||||
{ | |||||
int ret = 0; | |||||
struct net_device *dev; | |||||
struct in_device *in_dev; | |||||
dev = dev_get_by_name(&init_net, interface); | |||||
if (!dev) { | |||||
pr_err("Not found interface: %s\n", interface); | |||||
return -ENOENT; | |||||
} | |||||
conf.interface_ifindex = dev->ifindex; | |||||
in_dev = inetdev_by_index(dev_net(dev), conf.interface_ifindex); | |||||
if (!in_dev) { | |||||
pr_err("Not found in_dev on %s\n", interface); | |||||
ret = -ENOENT; | |||||
goto QUIT; | |||||
} | |||||
for_primary_ifa(in_dev) { | |||||
conf.interface_ipaddr = ifa->ifa_local; | |||||
conf.interface_mask = ifa->ifa_mask; | |||||
conf.interface_broadcast = ifa->ifa_broadcast; | |||||
pr_info("Found ip from %s: %pI4\n", interface, &conf.interface_ipaddr); | |||||
break; | |||||
} endfor_ifa(in_dev) | |||||
QUIT: | |||||
dev_put(dev); | |||||
return ret; | |||||
} | |||||
static int proc_config_show(struct seq_file *s, void *v) | |||||
{ | |||||
seq_printf(s, "enabled(RW) = %d\n", conf.enabled); | |||||
seq_printf(s, "interface(RW) = %s\n", conf.interface); | |||||
seq_printf(s, "ipaddr(RO) = %pI4\n", &conf.interface_ipaddr); | |||||
seq_printf(s, "netmask(RO) = %pI4\n", &conf.interface_mask); | |||||
seq_printf(s, "broadcast(RO) = %pI4\n", &conf.interface_broadcast); | |||||
seq_printf(s, "port(RW) = %d\n", conf.port); | |||||
seq_printf(s, "ssl_port(RW) = %d\n", conf.ssl_port); | |||||
return 0; | |||||
} | |||||
static ssize_t proc_config_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) | |||||
{ | |||||
char data[128]; | |||||
char *delim, *key; | |||||
const char *value; | |||||
int update = 0; | |||||
if (size == 0) | |||||
return -EINVAL; | |||||
if (size > sizeof(data)) | |||||
size = sizeof(data); | |||||
if (copy_from_user(data, buf, size)) | |||||
return -EFAULT; | |||||
data[size - 1] = 0; | |||||
key = data; | |||||
while (key && *key) { | |||||
while (*key && (*key == ' ')) | |||||
key++; | |||||
delim = strchr(key, '='); | |||||
if (!delim) | |||||
break; | |||||
*delim++ = 0; | |||||
value = delim; | |||||
delim = strchr(value, '\n'); | |||||
if (delim) | |||||
*delim++ = 0; | |||||
if (!strcmp(key, "enabled")) { | |||||
conf.enabled = simple_strtol(value, NULL, 0); | |||||
if (conf.enabled) | |||||
update = 1; | |||||
pr_info("wifidog %s\n", conf.enabled ? "enabled" : "disabled"); | |||||
} else if (!strcmp(key, "interface")) { | |||||
strncpy(conf.interface, value, sizeof(conf.interface) - 1); | |||||
update = 1; | |||||
} else if (!strcmp(key, "port")) { | |||||
conf.port = simple_strtol(value, NULL, 0); | |||||
} else if (!strcmp(key, "ssl_port")) { | |||||
conf.ssl_port = simple_strtol(value, NULL, 0); | |||||
} | |||||
key = delim; | |||||
} | |||||
if (update) | |||||
update_gw_interface(conf.interface); | |||||
return size; | |||||
} | |||||
static int proc_config_open(struct inode *inode, struct file *file) | |||||
{ | |||||
return single_open(file, proc_config_show, NULL); | |||||
} | |||||
const static struct file_operations proc_config_ops = { | |||||
.owner = THIS_MODULE, | |||||
.open = proc_config_open, | |||||
.read = seq_read, | |||||
.write = proc_config_write, | |||||
.llseek = seq_lseek, | |||||
.release = single_release | |||||
}; | |||||
int init_config(void) | |||||
{ | |||||
int ret = 0; | |||||
conf.interface_ifindex= -1; | |||||
conf.port = 2060; | |||||
conf.ssl_port = 8443; | |||||
strcpy(conf.interface, "br-lan"); | |||||
proc = proc_mkdir(PROC_DIR_NAME, NULL); | |||||
if (!proc) { | |||||
pr_err("can't create dir /proc/"PROC_DIR_NAME"/\n"); | |||||
return -ENODEV;; | |||||
} | |||||
if (!proc_create("config", 0644, proc, &proc_config_ops)) { | |||||
pr_err("can't create file /proc/"PROC_DIR_NAME"/config\n"); | |||||
ret = -EINVAL; | |||||
goto remove; | |||||
} | |||||
return 0; | |||||
remove: | |||||
remove_proc_entry(PROC_DIR_NAME, NULL); | |||||
return ret; | |||||
} | |||||
void deinit_config(void) | |||||
{ | |||||
remove_proc_entry("config", proc); | |||||
remove_proc_entry(PROC_DIR_NAME, NULL); | |||||
} | |||||
struct config *get_config(void) | |||||
{ | |||||
return &conf; | |||||
} |
@ -0,0 +1,31 @@ | |||||
/* | |||||
* Copyright (C) 2017 jianhui zhao <jianhuizhao329@gmail.com> | |||||
* | |||||
* This program is free software; you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License version 2 as | |||||
* published by the Free Software Foundation. | |||||
*/ | |||||
#ifndef __CONFIG_H_ | |||||
#define __CONFIG_H_ | |||||
#include <linux/proc_fs.h> | |||||
#define PROC_DIR_NAME "wifidog-ng" | |||||
struct config { | |||||
int enabled; | |||||
char interface[32]; | |||||
int interface_ifindex; | |||||
__be32 interface_ipaddr; | |||||
__be32 interface_mask; | |||||
__be32 interface_broadcast; | |||||
int port; | |||||
int ssl_port; | |||||
}; | |||||
int init_config(void); | |||||
void deinit_config(void); | |||||
struct config *get_config(void); | |||||
#endif |
@ -0,0 +1,176 @@ | |||||
/* | |||||
* Copyright (C) 2017 jianhui zhao <jianhuizhao329@gmail.com> | |||||
* | |||||
* This program is free software; you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License version 2 as | |||||
* published by the Free Software Foundation. | |||||
*/ | |||||
#include <linux/init.h> | |||||
#include <linux/module.h> | |||||
#include <linux/version.h> | |||||
#include <linux/ip.h> | |||||
#include <linux/tcp.h> | |||||
#include <linux/udp.h> | |||||
#include <net/netfilter/nf_nat.h> | |||||
#include <net/netfilter/nf_nat_l3proto.h> | |||||
#include "utils.h" | |||||
#include "config.h" | |||||
#define IPS_HIJACKED (1 << 31) | |||||
#define IPS_ALLOWED (1 << 30) | |||||
static u32 wd_nf_nat_setup_info(void *priv, struct sk_buff *skb, | |||||
const struct nf_hook_state *state, struct nf_conn *ct) | |||||
{ | |||||
struct config *conf = get_config(); | |||||
struct tcphdr *tcph = tcp_hdr(skb); | |||||
union nf_conntrack_man_proto proto; | |||||
struct nf_nat_range newrange; | |||||
static uint16_t PORT_80 = htons(80); | |||||
proto.tcp.port = (tcph->dest == PORT_80) ? htons(conf->port) : htons(conf->ssl_port); | |||||
newrange.flags = NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED; | |||||
newrange.min_addr.ip = newrange.max_addr.ip = conf->interface_ipaddr; | |||||
newrange.min_proto = newrange.max_proto = proto; | |||||
ct->status |= IPS_HIJACKED; | |||||
return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST); | |||||
} | |||||
static u32 wifidog_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) | |||||
{ | |||||
struct config *conf = get_config(); | |||||
struct iphdr *iph = ip_hdr(skb); | |||||
struct nf_conn *ct; | |||||
struct tcphdr *tcph; | |||||
struct udphdr *udph; | |||||
enum ip_conntrack_info ctinfo; | |||||
static uint16_t PORT_80 = htons(80); /* http */ | |||||
static uint16_t PORT_443 = htons(443); /* https */ | |||||
static uint16_t PORT_67 = htons(67); /* dhcp */ | |||||
static uint16_t PORT_53 = htons(53); /* dns */ | |||||
if (unlikely(!conf->enabled)) | |||||
return NF_ACCEPT; | |||||
if (state->in->ifindex != conf->interface_ifindex) | |||||
return NF_ACCEPT; | |||||
/* Accept broadcast */ | |||||
if (skb->pkt_type == PACKET_BROADCAST || skb->pkt_type == PACKET_MULTICAST) | |||||
return NF_ACCEPT; | |||||
/* Accept all to local area networks */ | |||||
if ((iph->daddr | ~conf->interface_mask) == conf->interface_broadcast) | |||||
return NF_ACCEPT; | |||||
ct = nf_ct_get(skb, &ctinfo); | |||||
if (!ct || (ct->status & IPS_ALLOWED)) | |||||
return NF_ACCEPT; | |||||
if (ct->status & IPS_HIJACKED) { | |||||
if (is_allowed_mac(skb, state)) { | |||||
/* Avoid duplication of authentication */ | |||||
nf_reset(skb); | |||||
nf_ct_kill(ct); | |||||
} | |||||
return NF_ACCEPT; | |||||
} else if (ctinfo == IP_CT_NEW && (is_allowed_dest_ip(skb, state) || is_allowed_mac(skb, state))) { | |||||
ct->status |= IPS_ALLOWED; | |||||
return NF_ACCEPT; | |||||
} | |||||
switch (iph->protocol) { | |||||
case IPPROTO_TCP: | |||||
tcph = tcp_hdr(skb); | |||||
if(tcph->dest == PORT_53 || tcph->dest == PORT_67) { | |||||
ct->status |= IPS_ALLOWED; | |||||
return NF_ACCEPT; | |||||
} | |||||
if (tcph->dest == PORT_80 || tcph->dest == PORT_443) | |||||
goto redirect; | |||||
else | |||||
return NF_DROP; | |||||
case IPPROTO_UDP: | |||||
udph = udp_hdr(skb); | |||||
if(udph->dest == PORT_53 || udph->dest == PORT_67) { | |||||
ct->status |= IPS_ALLOWED; | |||||
return NF_ACCEPT; | |||||
} | |||||
return NF_DROP; | |||||
default: | |||||
ct->status |= IPS_ALLOWED; | |||||
return NF_ACCEPT; | |||||
} | |||||
redirect: | |||||
/* all packets from unknown client are dropped */ | |||||
if (ctinfo != IP_CT_NEW || (ct->status & IPS_DST_NAT_DONE)) { | |||||
pr_debug("dropping packets of suspect stream, src:%pI4, dst:%pI4\n", &iph->saddr, &iph->daddr); | |||||
return NF_DROP; | |||||
} | |||||
return nf_nat_ipv4_in(priv, skb, state, wd_nf_nat_setup_info); | |||||
} | |||||
static struct nf_hook_ops wifidog_ops[] __read_mostly = { | |||||
{ | |||||
.hook = wifidog_hook, | |||||
.pf = PF_INET, | |||||
.hooknum = NF_INET_PRE_ROUTING, | |||||
.priority = NF_IP_PRI_CONNTRACK + 1 /* after conntrack */ | |||||
} | |||||
}; | |||||
static int __init wifidog_init(void) | |||||
{ | |||||
int ret; | |||||
ret = init_config(); | |||||
if (ret) | |||||
return ret; | |||||
#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 12, 14) | |||||
ret = nf_register_net_hooks(&init_net, wifidog_ops, ARRAY_SIZE(wifidog_ops)); | |||||
#else | |||||
ret = nf_register_hooks(wifidog_ops, ARRAY_SIZE(wifidog_ops)); | |||||
#endif | |||||
if (ret < 0) { | |||||
pr_err("can't register hook\n"); | |||||
goto remove_config; | |||||
} | |||||
pr_info("kmod of wifidog is started\n"); | |||||
return 0; | |||||
remove_config: | |||||
deinit_config(); | |||||
return ret; | |||||
} | |||||
static void __exit wifidog_exit(void) | |||||
{ | |||||
deinit_config(); | |||||
#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 12, 14) | |||||
nf_unregister_net_hooks(&init_net, wifidog_ops, ARRAY_SIZE(wifidog_ops)); | |||||
#else | |||||
nf_unregister_hooks(wifidog_ops, ARRAY_SIZE(wifidog_ops)); | |||||
#endif | |||||
pr_info("kmod of wifidog-ng is stop\n"); | |||||
} | |||||
module_init(wifidog_init); | |||||
module_exit(wifidog_exit); | |||||
MODULE_AUTHOR("jianhui zhao <jianhuizhao329@gmail.com>"); | |||||
MODULE_LICENSE("GPL"); |
@ -0,0 +1,60 @@ | |||||
/* | |||||
* Copyright (C) 2017 jianhui zhao <jianhuizhao329@gmail.com> | |||||
* | |||||
* This program is free software; you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License version 2 as | |||||
* published by the Free Software Foundation. | |||||
*/ | |||||
#ifndef __UTILS_H_ | |||||
#define __UTILS_H_ | |||||
#include <linux/netfilter/ipset/ip_set.h> | |||||
static inline int wd_ip_set_test(const char *name, const struct sk_buff *skb, | |||||
struct ip_set_adt_opt *opt, const struct nf_hook_state *state) | |||||
{ | |||||
static struct xt_action_param par = { }; | |||||
struct ip_set *set = NULL; | |||||
ip_set_id_t index; | |||||
int ret; | |||||
index = ip_set_get_byname(state->net, name, &set); | |||||
if (!set) | |||||
return 0; | |||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) | |||||
par.net = state->net; | |||||
#else | |||||
par.state = state; | |||||
#endif | |||||
ret = ip_set_test(index, skb, &par, opt); | |||||
ip_set_put_byindex(state->net, index); | |||||
return ret; | |||||
} | |||||
static inline int is_allowed_mac(struct sk_buff *skb, const struct nf_hook_state *state) | |||||
{ | |||||
static struct ip_set_adt_opt opt = { | |||||
.family = NFPROTO_IPV4, | |||||
.dim = IPSET_DIM_ONE, | |||||
.flags = IPSET_DIM_ONE_SRC, | |||||
.ext.timeout = UINT_MAX, | |||||
}; | |||||
return wd_ip_set_test("wifidog-ng-mac", skb, &opt, state); | |||||
} | |||||
static inline int is_allowed_dest_ip(struct sk_buff *skb, const struct nf_hook_state *state) | |||||
{ | |||||
static struct ip_set_adt_opt opt = { | |||||
.family = NFPROTO_IPV4, | |||||
.dim = IPSET_DIM_ONE, | |||||
.ext.timeout = UINT_MAX, | |||||
}; | |||||
return wd_ip_set_test("wifidog-ng-ip", skb, &opt, state); | |||||
} | |||||
#endif |