ホームネットワークのロックの透過的なバイパス

最近のニュースにより、インターネットリソースをブロックする問題が再び鮮明になりました。 一方では、 それらを回避する方法について 多くのことが 書かれ ており 、もう一度このトピックを噛む必要はないようです。 一方、定期的に追加のアクションを実行して目的のリソースにアクセスすることは、ITスペシャリストを満足させるものではありません(常に近くにいる人が対処できるものではありません)。



ユーザー向けのシンプルで透過的なソリューションが必要です。これを設定すると、次のcopywriter-plagiaristの要求で今日ブロックされたものを考慮することなく、インターネットを簡単に使用できます。



このアイデアは、すでにホームルーターにあるロックをバイパスすることを示唆しています。



実際、ルーターでトラフィックを拾い、VPNを介してすべてのトラフィックを駆動するのは簡単です。また、一部のVPNプロバイダーは、OpenWrtを設定してそれらを操作するための段階的な指示さえ持っています。



ただし、VPNサービスの速度は依然としてインターネットアクセスの速度よりも遅れており、VPNサービスには費用がかかるか、多くの制限があるか、定期的に新しいログインを受信する必要があります。 財務と時間の両方のコスト最適化の観点から、Torは望ましいように見えますが、その速度はさらに悪く、Torを通じて急流を急流することは全く良い考えではありません。



終了-ブロックされたトラフィックのみをVPN / Torにリダイレクトし、残りを通常の方法でスキップします。



注意:このスキームは、ブロックされたサイトを表示するための匿名性を提供しません。外部リンクは実際のIPを明らかにします。



OpenWrtの特定の実装は、記事の最後に記載されています。 詳細や代替ソリューションに興味がない場合は、右にスクロールできます。



トンネルへのトラフィックのトンネリングとリダイレクト



VPNまたはTorの設定は難しくありません。 Torは、透過プロキシとして構成する必要があります(または、torとtun2socksの束をセットアップする必要があります)。 なぜなら 最終的な目標はPKNロックをバイパスすることです<ExcludeExitNodes {RU}



では、ロシア連邦の領土( <ExcludeExitNodes {RU}



)での出力ノードの使用を禁止することをお勧めします。



Torでは、トラフィックはルールによってリダイレクトからnat



netfilterテーブルのPREROUTING



透過プロキシポートにリダイレクトされます。



VPN(またはTor + tun2socks)へのリダイレクトの場合、トラフィックはmangle



テーブルでマークされ、ラベルを使用して、トラフィックを適切なインターフェイスにリダイレクトするルーティングテーブル選択します。

どちらの場合も、(時間)ブロックの対象となるホストをipset



は、トラフィックを分類するために使用されます。



ipset



c(times)ブロックされたホストの形成



残念ながら、ipsetの「レジストリからすべてのIPを駆動する」オプションは、希望どおりに機能しません。まず、ブロックされたホストのすべてのIPアドレスがリストに存在するわけではなく、次に、ブロックから逃れようとして、リソースのIPアドレスが変更される可能性があります(およびプロバイダーすでにこれを知っていますが、私たちはまだ知りません)、第三に、同じ共有ホスティングにあるサイトの誤検知。



私は庭を何らかの種類のdpiでフェンスしたくありません。結局、かなり弱いハードウェアで動作するはずです。 解決方法は非常にシンプルでエレガントです:dnsmasq(ルーターに既にインストールされている可能性が最も高いDNSサーバー)は、名前を解決するときにIPアドレスを対応するipsetに追加できます(configで同じ名前のオプション)。 まさに必要なもの:ロック解除する必要のあるすべてのドメインを構成に追加し、必要に応じて、dnsmasq自体がブロックされたリソースへのアクセス先のIPアドレスをipsetに正確に追加します。



私はdnsmasqが起動し、5万行の構成で正常に動作するかどうか疑問に思っていました(縮小して揺らした後のレジストリのエントリとほぼ同じ数)が、幸いなことにそれらは根拠のないものでした。



軟膏のフライは、リストを更新するときにdnsmasqを再起動する必要があるということです。 SIGHUPでは、設定をオーバーロードしません。



ドメインのリスト



可能な限り自動的に発生するはずです。



最初のオプション(この例で実装されています):単一のロックレジスタに基づいてリストを作成し、cronで更新します。

Roskomnadzorはロックの登録簿を一般に提供していませんが、世界には良い人がいないわけではなく、少なくとも2つの リソースを見つけることができます。 そして、素晴らしい、彼らはまたAPIを持っています。 リストを解析するときは、ドメイン名のリストに、ドメイン名自体に加えて、IPアドレスも存在することを考慮する必要があります。 これらは個別に処理する必要があります(または、スコアを付けます:リストの約0.1%であり、関心のあるリソースにつながる可能性は低いです)。 キリル文字ドメインは、常にpunycodeで表されるとは限りません。 リストのかなりの部分が1つの第2レベルドメインのサブドメインによって占有されており、www / wwwのないドメインと単純に重複したエントリが示されています。 上記のすべては、rublacklist.netのリストにより関連しています(さらに、奇妙であり、誤ってシールドされていることもあります)。 彼にとっては、リストをほぼ2回正規化および圧縮し、巨大なluaスクリプト(下記を参照)を作成する必要がありました。 C antizapret.info状況ははるかに良く、awkの1行で実行できます。



他の方法もあります。ブロックされたリソースにアクセスする多くのプロバイダーは、アクセスの制限に関するスタブにリダイレクトします。 たとえば、 http://block.mts.ru/?host=/host/&url=/url/&params=/params/



。 同じdnsmasq( address=/block.mts.ru/192.168.1.1



)を使用しaddress=/block.mts.ru/192.168.1.1



A-record block.mts.ruをルーターのWebサーバーのアドレスにaddress=/block.mts.ru/192.168.1.1



(そして簡単なスクリプトを配置して)、ブロックされたネットワークユーザーのリストをローカルで生成できます。リソースをdnsmasq構成に追加し、nslookupを再度実行して(IPアドレスがipsetに追加されるように)、ユーザーを元のURLに再度リダイレクトします。 しかし、毎回dnsmasqを同時に再起動する必要性はやや湿っています。 はい。httpでのみ機能します。



軟膏がもう1つ飛ぶようになりました。一部のプロバイダーは、Webサイトのリストにリストされているサイトに加えて、不正にブロックし、公式にリストされていないことに気付きました。 同時に、彼らは静かな鼻landでそれをブロックし、プラグを取り外しません。 だから絶対に手動のドライブはできません。



追加のメモ



プロバイダーのDNSサーバーをアップストリームサーバーとして使用しないでください。 ブロッキングは、リソースの名前を解決する段階で発生する可能性があります。 プロバイダーサーバーを探しているアドレス、つまりCNAME block.mts.ruに渡します。 最も単純なソリューションは、 server=8.8.8.8, server=8.8.4.4



です。 私が個人的には観察していないサードパーティのサーバーのDNS応答のプロバイダーによる変更。 それらが開始した場合、制限されたリストから別のアップストリームに(VPN / Torを介して)ドメインリクエストを送信できますが、必要なしに構成を拡張することはできません。



Torを使用する場合、ボーナスとして.onionサイトでのサーフィンのボーナスを得ることができます。Torは、組み込みのDNSサーバーを介して名前を解決すると、所定の範囲の仮想アドレスに表示します。 次に、このアドレスへのアクセスをTor'aおよびvoilaプロキシにリダイレクトするだけです。 ただし、トラフィックの選択的トンネリングとの接続では匿名性が得られないことを再度お知らせします。



OpenWrt実装(15.05)



特にTorを使用している場合、ルーター自体は最悪ではありません。 MIPS 400MHz @ 32MB RAMは、検討する価値があります。



USBポートがある場合、内蔵フラッシュの欠如はUSBフラッシュドライブで補うことができます (一般的に、定期的に書き換え可能なデータに内蔵フラッシュを使用しないことはかなり堅牢な考えだと思います)。



通常、OpenWrtファームウェアには、ipsetの方法がわからない切り捨てられたdnsmasqが含まれています。 これをdnsmasq-fullに置き換える必要があります。



デフォルトでは存在しないパッケージのうち、 ipsettor、およびtor-geoipも必要です。

luasocketパッケージが必要であるか、または(厳密なフラッシュ保存モードで)/ usr / lib / luaフォルダーにあるltn12.luaが別途必要です。 キリル文字ドメインをutf8からpunycodeに変換するには、/ usr / lib / luaのidn.lualuabitopパッケージが必要です (またはスクリプト構成のオプションを無効にします)。



ブロックリスト更新スクリプト


/usr/bin/rublupdate.lua
 local config = { blSource = "antizapret", -- antizapret  rublacklist groupBySld = 32, --             neverGroupMasks = { "^%a%a%a?.%a%a$" }, --    org.ru, net.ua   neverGroupDomains = { ["livejournal.com"] = true, ["facebook.com"] = true , ["vk.com"] = true }, stripWww = true, convertIdn = true, torifyNsLookups = false, --  DNS     TOR blMinimumEntries = 1000, --     ,  -        dnsmasqConfigPath = "/etc/runblock/runblock.dnsmasq", ipsetConfigPath = "/etc/runblock/runblock.ipset", ipsetDns = "rublack-dns", ipsetIp = "rublack-ip", torDnsAddr = "127.0.0.1#9053" } local function prequire(package) local result, err = pcall(function() require(package) end) if not result then return nil, err end return require(package) -- return the package value end local idn = prequire("idn") if (not idn) and (config.convertIdn) then error("you need either put idn.lua (github.com/haste/lua-idn) in script dir or set 'convertIdn' to false") end local http = prequire("socket.http") if not http then local ltn12 = require("ltn12") end if not ltn12 then error("you need either install luasocket package (prefered) or put ltn12.lua in script dir") end local function hex2unicode(code) local n = tonumber(code, 16) if (n < 128) then return string.char(n) elseif (n < 2048) then return string.char(192 + ((n - (n % 64)) / 64), 128 + (n % 64)) else return string.char(224 + ((n - (n % 4096)) / 4096), 128 + (((n % 4096) - (n % 64)) / 64), 128 + (n % 64)) end end local function rublacklistExtractDomains() local currentRecord = "" local buffer = "" local bufferPos = 1 local streamEnded = false return function(chunk) local retVal = "" if chunk == nil then streamEnded = true else buffer = buffer .. chunk end while true do local escapeStart, escapeEnd, escapedChar = buffer:find("\\(.)", bufferPos) if escapedChar then currentRecord = currentRecord .. buffer:sub(bufferPos, escapeStart - 1) bufferPos = escapeEnd + 1 if escapedChar == "n" then retVal = currentRecord break elseif escapedChar == "u" then currentRecord = currentRecord .. "\\u" else currentRecord = currentRecord .. escapedChar end else currentRecord = currentRecord .. buffer:sub(bufferPos, #buffer) buffer = "" bufferPos = 1 if streamEnded then if currentRecord == "" then retVal = nil else retVal = currentRecord end end break end end if retVal and (retVal ~= "") then currentRecord = "" retVal = retVal:match("^[^;]*;([^;]+);[^;]*;[^;]*;[^;]*;[^;]*.*$") if retVal then retVal = retVal:gsub("\\u(%x%x%x%x)", hex2unicode) else retVal = "" end end return (retVal) end end local function antizapretExtractDomains() local currentRecord = "" local buffer = "" local bufferPos = 1 local streamEnded = false return function(chunk) local haveOutput = 0 local retVal = "" if chunk == nil then streamEnded = true else buffer = buffer .. chunk end local newlinePosition = buffer:find("\n", bufferPos) if newlinePosition then currentRecord = currentRecord .. buffer:sub(bufferPos, newlinePosition - 1) bufferPos = newlinePosition + 1 retVal = currentRecord else currentRecord = currentRecord .. buffer:sub(bufferPos, #buffer) buffer = "" bufferPos = 1 if streamEnded then if currentRecord == "" then retVal = nil else retVal = currentRecord end end end if retVal and (retVal ~= "") then currentRecord = "" end return (retVal) end end local function normalizeFqdn() return function(chunk) if chunk and (chunk ~= "") then if config["stripWww"] then chunk = chunk:gsub("^www%.", "") end if idn and config["convertIdn"] then chunk = idn.encode(chunk) end if #chunk > 255 then chunk = "" end chunk = chunk:lower() end return (chunk) end end local function cunstructTables(bltables) bltables = bltables or { fqdn = {}, sdcount = {}, ips = {} } local f = function(blEntry, err) if blEntry and (blEntry ~= "") then if blEntry:match("^%d+%.%d+%.%d+%.%d+$") then -- ip  -     iptables if not bltables.ips[blEntry] then bltables.ips[blEntry] = true end else --   , FQDN  .    2  (  bl   TLD -   :)) local subDomain, secondLevelDomain = blEntry:match("^([a-z0-9%-%.]-)([a-z0-9%-]+%.[a-z0-9%-]+)$") if secondLevelDomain then bltables.fqdn[blEntry] = secondLevelDomain if 1 > 0 then bltables.sdcount[secondLevelDomain] = (bltables.sdcount[secondLevelDomain] or 0) + 1 end end end end return 1 end return f, bltables end local function compactDomainList(fqdnList, subdomainsCount) local domainTable = {} local numEntries = 0 if config.groupBySld and (config.groupBySld > 0) then for sld in pairs(subdomainsCount) do if config.neverGroupDomains[sld] then subdomainsCount[sld] = 0 break end for _, pattern in ipairs(config.neverGroupMasks) do if sld:find(pattern) then subdomainsCount[sld] = 0 break end end end end for fqdn, sld in pairs(fqdnList) do if (not fqdnList[sld]) or (fqdn == sld) then local keyValue; if config.groupBySld and (config.groupBySld > 0) and (subdomainsCount[sld] > config.groupBySld) then keyValue = sld else keyValue = fqdn end if not domainTable[keyValue] then domainTable[keyValue] = true numEntries = numEntries + 1 end end end return domainTable, numEntries end local function generateDnsmasqConfig(configPath, domainList) local configFile = assert(io.open(configPath, "w"), "could not open dnsmasq config") for fqdn in pairs(domainList) do if config.torifyNsLookups then configFile:write(string.format("server=/%s/%s\n", fqdn, config.torDnsAddr)) end configFile:write(string.format("ipset=/%s/%s\n", fqdn, config.ipsetDns)) end configFile:close() end local function generateIpsetConfig(configPath, ipList) local configFile = assert(io.open(configPath, "w"), "could not open ipset config") configFile:write(string.format("flush %s-tmp\n", config.ipsetIp)) for ipaddr in pairs(ipList) do configFile:write(string.format("add %s %s\n", config.ipsetIp, ipaddr)) end configFile:write(string.format("swap %s %s-tmp\n", config.ipsetIp, config.ipsetIp)) configFile:close() end local retVal, retCode, url local output, bltables = cunstructTables() if config.blSource == "rublacklist" then output = ltn12.sink.chain(ltn12.filter.chain(rublacklistExtractDomains(), normalizeFqdn()), output) url = "http://reestr.rublacklist.net/api/current" elseif config.blSource == "antizapret" then output = ltn12.sink.chain(ltn12.filter.chain(antizapretExtractDomains(), normalizeFqdn()), output) url = "http://api.antizapret.info/group.php?data=domain" else error("blacklist source should be either 'rublacklist' or 'antizapret'") end if http then retVal, retCode = http.request { url = url, sink = output } else retVal, retCode = ltn12.pump.all(ltn12.source.file(io.popen("wget -qO- " .. url)), output) end if (retVal == 1) and ((retCode == 200) or (http == nil)) then local domainTable, recordsNum = compactDomainList(bltables.fqdn, bltables.sdcount) if recordsNum > config.blMinimumEntries then generateDnsmasqConfig(config.dnsmasqConfigPath, domainTable) generateIpsetConfig(config.ipsetConfigPath, bltables.ips) print(string.format("blacklists updated. %d entries.", recordsNum)) os.exit(0) end end os.exit(1)
      
      







Dnsmasq設定


/etc/dnsmasq.conf
 server=/onion/127.0.0.1#9053 ipset=/onion/onion conf-file=/etc/runblock/runblock.dnsmasq
      
      







dnsmasq / etc / config / dhcpセクションに追加します
  list server '8.8.8.8' list server '8.8.4.4' list rebind_domain 'onion'
      
      







Netfilter設定


/ etc / config / firewallに追加します
 config ipset option name 'rublack-dns' option storage 'hash' option match 'dest_ip' option timeout '86400' config ipset option name 'rublack-ip' option storage 'hash' option match 'dest_ip' config ipset option name 'rublack-ip-tmp' option storage 'hash' option match 'dest_ip' config ipset option name 'onion' option storage 'hash' option match 'dest_ip' option timeout '86400' config redirect option name 'torify-blocked-dns' option src 'lan' option proto 'tcp' option ipset 'rublack-dns' option dest_port '9040' option dest 'lan' config redirect option name 'torify-blocked-ip' option src 'lan' option proto 'tcp' option ipset 'rublack-ip' option dest_port '9040' option dest 'lan' config redirect option name 'torify-onion' option src 'lan' option proto 'tcp' option ipset 'onion' option dest_port '9040' option dest 'lan'
      
      







/etc/firewall.userに追加します
 cat /etc/runblock/runblock.ipset | ipset restore
      
      







Tor設定


/ etc / torrc
 User tor PidFile /var/run/tor.pid DataDirectory /var/lib/tor ExcludeExitNodes {RU} VirtualAddrNetwork 10.254.0.0/16 #    .onion  AutomapHostsOnResolve 1 TransPort 9040 TransListenAddress 127.0.0.1 TransListenAddress 192.168.1.1 # LAN  DNSPort 9053 DNSListenAddress 127.0.0.1 #AvoidDiskWrites 1 #  OpenWrt /var    RAM (tmpfs)  ,     
      
      







/etc/runblock



を作成し、 lua /usr/bin/rublupdate.lua



スクリプトを手動でlua /usr/bin/rublupdate.lua



実行し、エラーなしで動作することを確認し、cronに追加して(1日数回で十分です)、Roscomnadzorを忘れます。 まあ、トーラス、またはレジストリを公開するサイトをブロックし始めるまで)。



All Articles