DNS over TLS-StunnelとLuaを使用してDNSクエリを暗号化します



画像ソース







DNS (ドメインネームシステム)は、ドメイン情報を取得するための分散コンピューターシステムです。



TLSトランスポート層セキュリティプロトコル)-インターネットノード間の安全なデータ転送を提供します。

「Google Public DNSがDNS over TLSサポートを静かにオンにした」というニュースの後、私は試してみることにしました。 暗号化されたTCPトンネルを作成するトンネルがあります。 ただし、プログラムは通常UDPプロトコルを使用してDNSと通信します。 したがって、TCPストリームとの間でUDPパケットを転送するプロキシが必要です。 Luaに書き込みます。







TCPとUDP DNSパケットの完全な違い:







4.2.2。 TCPの使用

TCP接続を介して送信されるメッセージは、サーバーポート53(10進数)を使用します。 メッセージには、2バイトの長さフィールドを除く、メッセージの長さを示す2バイトの長さフィールドがプレフィックスとして付けられます。 この長さフィールドを使用すると、低レベル処理で解析を開始する前に完全なメッセージを組み立てることができます。

RFC1035:ドメイン名-実装と仕様







つまり、そこで行うことです。







  1. UDPからパケットを取得します
  2. このパケットのサイズが示されている数バイトを最初に追加します
  3. TCPチャネルに送信


そして反対方向に:







  1. TCPから数バイトを読み取り、パケットサイズを取得します
  2. TCPからパケットを読み取ります
  3. UDP経由で受信者に送信する


Stunnelをカスタマイズする



  1. Stunnel設定のあるディレクトリにルート証明書Root-R2.crtをダウンロードします
  2. 証明書をPEMに変換する

    openssl x509 -inform DER -in Root-R2.crt -out Root-R2.pem -text
          
          



  3. stunnel.confに書き込みます。







     [dns] client = yes accept = 127.0.0.1:53 connect = 8.8.8.8:853 CAfile = Root-R2.pem verifyChain = yes checkIP = 8.8.8.8
          
          







つまり、Stunnel:







  1. 127.0.0.1:53で暗号化されていないTCPを受け入れます
  2. 8.8.8.8:853(Google DNS)のアドレスに暗号化されたTLSトンネルを開きます
  3. データをやり取りします


Stunnelを起動する







トンネルの動作は、次のコマンドで確認できます。







 nslookup -vc ya.ru 127.0.0.1
      
      





-vcオプションは、nslookupがUDPの代わりにDNSサーバーへのTCP接続を使用するように強制します。







結果:







 *** Can't find server name for address 127.0.0.1: Non-existent domain Server: UnKnown Address: 127.0.0.1 Non-authoritative answer: Name: ya.ru Address: ( IP )
      
      





スクリプトを書く



私はLua 5.3で書いています。 数値を使用したバイナリ演算はすでに利用可能です。 さて、 Lua Socketモジュールが必要になります。







ファイル名: simple-udp-to-tcp-dns-proxy.lua







 local socket = require "socket" --  lua socket
      
      





--[[--









コンソールにダンプパッケージを送信できるようにする簡単な関数を作成しましょう。 プロキシが何をするのか見たいです。







--]]--









 function serialize(data) --       az  0-9    HEX  '\xFF' return "'"..data:gsub("[^a-z0-9-]", function(chr) return ("\\x%02X"):format(chr:byte()) end).."'" end
      
      





--[[--









UDPからTCPへ、およびその逆



2つのデータ伝送チャネルで動作する2つの関数を作成します。







--]]--









 --    UDP   TCP  function udp_to_tcp_coroutine_function(udp_in, tcp_out, clients) repeat coroutine.yield() --     packet, err_ip, port = udp_in:receivefrom() --  UDP  if packet then -- > - big endian -- I - unsigned integer -- 2 - 2 bytes size tcp_out:send(((">I2"):pack(#packet))..packet) --       TCP local id = (">I2"):unpack(packet:sub(1,2)) --  ID  if not clients[id] then clients[id] = {} end table.insert(clients[id] ,{ip=err_ip, port=port, packet=packet}) --    print(os.date("%c", os.time()) ,err_ip, port, ">", serialize(packet)) --     end until false end --    TCP      UDP function tcp_to_udp_coroutine_function(tcp_in, udp_out, clients) repeat coroutine.yield() --     -- > - big endian -- I - unsigned integer -- 2 - 2 bytes size local packet = tcp_in:receive((">I2"):unpack(tcp_in:receive(2)), nil) --  TCP  local id = (">I2"):unpack(packet:sub(1,2)) --  ID  if clients[id] then for key, client in pairs(clients[id]) do --  query     if packet:find(client.packet:sub(13, -1), 13, true) == 13 then --   udp_out:sendto(packet, client.ip, client.port) --     UDP clients[id][key] = nil --   --     print(os.date("%c", os.time()) ,client.ip, client.port, "<", serialize(packet)) break end end if not next(clients[id]) then clients[id] = nil end end until false end
      
      





--[[--









両方の関数は、起動直後にcoroutine.yield()を実行します。 これにより、関数のパラメーターを最初の呼び出しに渡し、追加パラメーターなしでcoroutine.resume(co)を実行できます。







メイン



そして、メインループを準備して開始するメイン関数です。







--]]--









 function main() local tcp_dns_socket = socket.tcp() --  TCP  local udp_dns_socket = socket.udp() --  UDP  local tcp_connected, err = tcp_dns_socket:connect("127.0.0.1", 53) --   TCP  assert(tcp_connected, err) --    print("tcp dns connected") --      local udp_open, err = udp_dns_socket:setsockname("127.0.0.1", 53) --  UDP  assert(udp_open, err) --    print("udp dns port open") --   UDP   --     Lua        nil --              local coroutines = { [tcp_dns_socket] = coroutine.create(tcp_to_udp_coroutine_function), --   TCP to UDP [udp_dns_socket] = coroutine.create(udp_to_tcp_coroutine_function) --   UDP to TCP } local clients = {} --      --        coroutine.resume(coroutines[tcp_dns_socket], tcp_dns_socket, udp_dns_socket, clients) coroutine.resume(coroutines[udp_dns_socket], udp_dns_socket, tcp_dns_socket, clients) --    socket.select        local socket_list = {tcp_dns_socket, udp_dns_socket} repeat --    -- socket.select   socket_list          --      .  for       for _, in_socket in ipairs(socket.select(socket_list)) do --       local ok, err = coroutine.resume(coroutines[in_socket]) if not ok then --       udp_dns_socket:close() --  UDP  tcp_dns_socket:close() --  TCP  print(err) --   return --    end end until false end
      
      





--[[--









メイン関数を起動します。 接続が突然閉じられた場合、1秒後にmainを呼び出して再び接続を確立します。







--]]--









 repeat local ok, err = coroutine.resume(coroutine.create(main)) --  main if not ok then print(err) end socket.sleep(1) --      until false
      
      





確認する



  1. スタンネルを実行する







  2. スクリプトを実行する







     lua5.3 simple-udp-to-tcp-dns-proxy.lua
          
          





  3. コマンドでスクリプトの動作を確認してください







     nslookup ya.ru 127.0.0.1
          
          





    今回は「-vc」を使用せずに、これがすでにUDPリクエストをTCPトンネルにラップするプロキシを作成および開始した方法です。









結果:







 *** Can't find server name for address 127.0.0.1: Non-existent domain Server: UnKnown Address: 127.0.0.1 Non-authoritative answer: Name: ya.ru Address: ( IP )
      
      





すべてが正常であれば、接続設定でDNSサーバー「127.0.0.1」として指定できます。







結論



現在、DNSクエリはTLSによって保護されています







PS私たちはグーグルに追加情報を提供しません



接続直後、Windowsはトンネルを介してGoogleのDNSサーバーに登録しようとします。 これは、チェックを外すことにより、詳細なDNS設定で無効になります。













time.windows.comへのリクエストがまだあります。 彼はもうそれほど個人的ではありませんが、私はそれをオフにする方法を見つけたことがありません。 自動時刻同期は無効になっています。







リンク



  1. RFC1035:ドメイン名-実装と仕様
  2. DNS over TLS
  3. simple-udp-to-tcp-dns-proxy.lua
  4. DNSクエリを手動で作成します



All Articles