├── README.md └── opt └── bin └── blupdate.lua /README.md: -------------------------------------------------------------------------------- 1 | ## Использование OpenVPN для обхода блокировок 2 | Решение ниже позволяет использовать VPN-соединение исключительно для списка блокируемых ресурсов. При обращении к любым другим ресурсам будет использоваться привычное провайдерское соединение. 3 | 4 | Детали установки и использования можно посмотреть увидеть в [Wiki](https://github.com/DontBeAPadavan/rublock-via-vpn/wiki). 5 | -------------------------------------------------------------------------------- /opt/bin/blupdate.lua: -------------------------------------------------------------------------------- 1 | #!/opt/bin/lua 2 | 3 | local config = { 4 | blSource = "antizapret", -- antizapret или rublacklist 5 | groupBySld = 16, -- количество поддоменов после которого в список вносится весь домен второго уровня целиком 6 | neverGroupMasks = { "^%a%a%a?.%a%a$" }, -- не распространять на org.ru, net.ua и аналогичные 7 | neverGroupDomains = { ["livejournal.com"] = true, ["facebook.com"] = true , ["vk.com"] = true }, 8 | stripWww = true, 9 | convertIdn = false, 10 | altNsLookups = true, -- отправлять DNS запросы заблокированных доменов через отдельный DNS 11 | blMinimumEntries = 1000, -- костыль если список получился короче, значит что-то пошло не так и конфиги не обновляем 12 | dnsmasqConfigPath = "/opt/etc/rublock.dnsmasq", 13 | ipsetConfigPath = "/opt/etc/rublock.ips", 14 | ipsetDns = "rublock", 15 | altDNSAddr = "8.8.8.8" 16 | } 17 | 18 | 19 | local function prequire(package) 20 | local result, err = pcall(function() require(package) end) 21 | if not result then 22 | return nil, err 23 | end 24 | return require(package) -- return the package value 25 | end 26 | 27 | local idn = prequire("idn") 28 | if (not idn) and (config.convertIdn) then 29 | error("you need either put idn.lua (github.com/haste/lua-idn) in script dir or set 'convertIdn' to false") 30 | end 31 | 32 | local http = prequire("socket.http") 33 | if not http then 34 | local ltn12 = require("ltn12") 35 | end 36 | if not ltn12 then 37 | error("you need either install luasocket package (prefered) or put ltn12.lua in script dir") 38 | end 39 | 40 | local function hex2unicode(code) 41 | local n = tonumber(code, 16) 42 | if (n < 128) then 43 | return string.char(n) 44 | elseif (n < 2048) then 45 | return string.char(192 + ((n - (n % 64)) / 64), 128 + (n % 64)) 46 | else 47 | return string.char(224 + ((n - (n % 4096)) / 4096), 128 + (((n % 4096) - (n % 64)) / 64), 128 + (n % 64)) 48 | end 49 | end 50 | 51 | local function rublacklistExtractDomains() 52 | local currentRecord = "" 53 | local buffer = "" 54 | local bufferPos = 1 55 | local streamEnded = false 56 | return function(chunk) 57 | local retVal = "" 58 | if chunk == nil then 59 | streamEnded = true 60 | else 61 | buffer = buffer .. chunk 62 | end 63 | 64 | while true do 65 | local escapeStart, escapeEnd, escapedChar = buffer:find("\\(.)", bufferPos) 66 | if escapedChar then 67 | currentRecord = currentRecord .. buffer:sub(bufferPos, escapeStart - 1) 68 | bufferPos = escapeEnd + 1 69 | if escapedChar == "n" then 70 | retVal = currentRecord 71 | break 72 | elseif escapedChar == "u" then 73 | currentRecord = currentRecord .. "\\u" 74 | else 75 | currentRecord = currentRecord .. escapedChar 76 | end 77 | else 78 | currentRecord = currentRecord .. buffer:sub(bufferPos, #buffer) 79 | buffer = "" 80 | bufferPos = 1 81 | if streamEnded then 82 | if currentRecord == "" then 83 | retVal = nil 84 | else 85 | retVal = currentRecord 86 | end 87 | end 88 | break 89 | end 90 | end 91 | if retVal and (retVal ~= "") then 92 | currentRecord = "" 93 | retVal = retVal:match("^[^;]*;([^;]+);[^;]*;[^;]*;[^;]*;[^;]*.*$") 94 | if retVal then 95 | retVal = retVal:gsub("\\u(%x%x%x%x)", hex2unicode) 96 | else 97 | retVal = "" 98 | end 99 | end 100 | return (retVal) 101 | end 102 | end 103 | 104 | local function antizapretExtractDomains() 105 | local currentRecord = "" 106 | local buffer = "" 107 | local bufferPos = 1 108 | local streamEnded = false 109 | return function(chunk) 110 | local haveOutput = 0 111 | local retVal = "" 112 | if chunk == nil then 113 | streamEnded = true 114 | else 115 | buffer = buffer .. chunk 116 | end 117 | local newlinePosition = buffer:find("\n", bufferPos) 118 | if newlinePosition then 119 | currentRecord = currentRecord .. buffer:sub(bufferPos, newlinePosition - 1) 120 | bufferPos = newlinePosition + 1 121 | retVal = currentRecord 122 | else 123 | currentRecord = currentRecord .. buffer:sub(bufferPos, #buffer) 124 | buffer = "" 125 | bufferPos = 1 126 | if streamEnded then 127 | if currentRecord == "" then 128 | retVal = nil 129 | else 130 | retVal = currentRecord 131 | end 132 | end 133 | end 134 | if retVal and (retVal ~= "") then 135 | currentRecord = "" 136 | end 137 | return (retVal) 138 | end 139 | end 140 | 141 | local function normalizeFqdn() 142 | return function(chunk) 143 | if chunk and (chunk ~= "") then 144 | if config["stripWww"] then chunk = chunk:gsub("^www%.", "") end 145 | if idn and config["convertIdn"] then chunk = idn.encode(chunk) end 146 | if #chunk > 255 then chunk = "" end 147 | chunk = chunk:lower() 148 | end 149 | return (chunk) 150 | end 151 | end 152 | 153 | local function cunstructTables(bltables) 154 | bltables = bltables or { fqdn = {}, sdcount = {}, ips = {} } 155 | local f = function(blEntry, err) 156 | if blEntry and (blEntry ~= "") then 157 | if blEntry:match("^%d+%.%d+%.%d+%.%d+$") then 158 | -- ip адреса - в отдельную таблицу для iptables 159 | if not bltables.ips[blEntry] then 160 | bltables.ips[blEntry] = true 161 | end 162 | else 163 | -- как можем проверяем, FQDN ли это. заодно выделяем домен 2 уровня (если в bl станут попадать TLD - дело плохо :)) 164 | local subDomain, secondLevelDomain = blEntry:match("^([a-z0-9%-%.]-)([a-z0-9%-]+%.[a-z0-9%-]+)$") 165 | if secondLevelDomain then 166 | bltables.fqdn[blEntry] = secondLevelDomain 167 | if 1 > 0 then 168 | bltables.sdcount[secondLevelDomain] = (bltables.sdcount[secondLevelDomain] or 0) + 1 169 | end 170 | end 171 | end 172 | end 173 | return 1 174 | end 175 | return f, bltables 176 | end 177 | 178 | local function compactDomainList(fqdnList, subdomainsCount) 179 | local domainTable = {} 180 | local numEntries = 0 181 | if config.groupBySld and (config.groupBySld > 0) then 182 | for sld in pairs(subdomainsCount) do 183 | if config.neverGroupDomains[sld] then 184 | subdomainsCount[sld] = 0 185 | break 186 | end 187 | for _, pattern in ipairs(config.neverGroupMasks) do 188 | if sld:find(pattern) then 189 | subdomainsCount[sld] = 0 190 | break 191 | end 192 | end 193 | end 194 | end 195 | for fqdn, sld in pairs(fqdnList) do 196 | if (not fqdnList[sld]) or (fqdn == sld) then 197 | local keyValue; 198 | if config.groupBySld and (config.groupBySld > 0) and (subdomainsCount[sld] > config.groupBySld) then 199 | keyValue = sld 200 | else 201 | keyValue = fqdn 202 | end 203 | if not domainTable[keyValue] then 204 | domainTable[keyValue] = true 205 | numEntries = numEntries + 1 206 | end 207 | end 208 | end 209 | return domainTable, numEntries 210 | end 211 | 212 | local function generateDnsmasqConfig(configPath, domainList) 213 | local configFile = assert(io.open(configPath, "w"), "could not open dnsmasq config") 214 | for fqdn in pairs(domainList) do 215 | if config.altNsLookups then 216 | configFile:write(string.format("server=/%s/%s\n", fqdn, config.altDNSAddr)) 217 | end 218 | configFile:write(string.format("ipset=/%s/%s\n", fqdn, config.ipsetDns)) 219 | end 220 | configFile:close() 221 | end 222 | 223 | local function generateIpsetConfig(configPath, ipList) 224 | local configFile = assert(io.open(configPath, "w"), "could not open ipset config") 225 | for ipaddr in pairs(ipList) do 226 | configFile:write(string.format("%s\n", ipaddr)) 227 | end 228 | configFile:close() 229 | end 230 | 231 | local retVal, retCode, url 232 | 233 | local output, bltables = cunstructTables() 234 | if config.blSource == "rublacklist" then 235 | output = ltn12.sink.chain(ltn12.filter.chain(rublacklistExtractDomains(), normalizeFqdn()), output) 236 | url = "http://reestr.rublacklist.net/api/current" 237 | elseif config.blSource == "antizapret" then 238 | output = ltn12.sink.chain(ltn12.filter.chain(antizapretExtractDomains(), normalizeFqdn()), output) 239 | url = "http://api.antizapret.info/group.php?data=domain" 240 | else 241 | error("blacklist source should be either 'rublacklist' or 'antizapret'") 242 | end 243 | 244 | if http then 245 | retVal, retCode = http.request { url = url, sink = output } 246 | else 247 | retVal, retCode = ltn12.pump.all(ltn12.source.file(io.popen("wget -qO- " .. url)), output) 248 | end 249 | 250 | if (retVal == 1) and ((retCode == 200) or (http == nil)) then 251 | local domainTable, recordsNum = compactDomainList(bltables.fqdn, bltables.sdcount) 252 | if recordsNum > config.blMinimumEntries then 253 | generateDnsmasqConfig(config.dnsmasqConfigPath, domainTable) 254 | generateIpsetConfig(config.ipsetConfigPath, bltables.ips) 255 | print(string.format("blacklists updated. %d entries.", recordsNum)) 256 | os.exit(0) 257 | end 258 | end 259 | os.exit(1) 260 | --------------------------------------------------------------------------------