📜 ⬆️ ⬇️

DNS over TLS - We encrypt our DNS queries using Stunnel and Lua


image source


DNS (English Domain Name System - domain name system) is a computer distributed system for obtaining information about domains.

TLS (English transport layer security) - provides secure data transmission between Internet nodes.

After the news "Google Public DNS silently turned on support for DNS over TLS" I decided to try it. I have a stunnel that will create an encrypted TCP tunnel. But programs usually communicate with DNS via the UDP protocol. Therefore, we need a proxy that will send UDP packets to the TCP stream and back. We will write it on Lua .


The difference between TCP and UDP DNS packets:


4.2.2. TCP usage
TCP port connections (decimal). The length of the field is given by the length of the field, which excludes the two byte length. This is a field.

RFC1035: DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION


That is, we do there:


  1. take a packet from UDP
  2. we add to it at the beginning a couple of bytes which indicate the size of this packet
  3. send to TCP channel

And in the opposite direction:


  1. read a couple of bytes from TCP, thereby obtaining the packet size
  2. read packet from TCP
  3. send it to the recipient via UDP

Configure Stunnel


  1. Download the root certificate Root-R2.crt to the directory with the Stunnel config
  2. Convert certificate to PEM
    openssl x509 -inform DER -in Root-R2.crt -out Root-R2.pem -text 
  3. We write to 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 


That is Stunnel:


  1. will accept unencrypted TCP at 127.0.0.1:53
  2. will open an encrypted TLS tunnel to the address 8.8.8.8:853 (Google DNS)
  3. will transfer data back and forth

Run Stunnel


Tunnel operation can be checked with the command:


 nslookup -vc ya.ru 127.0.0.1 

The option '-vc' causes nslookup to use a TCP connection to a DNS server instead of UDP.


Result:


 *** 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 ) 

We write a script


I am writing on Lua 5.3 . It already has binary operations with numbers. Well, we need the Lua Socket module.


File Name: simple-udp-to-tcp-dns-proxy.lua


 local socket = require "socket" --  lua socket 

--[[--


Let's write a simple function that allows you to send a package dump to the console. I want to see what the proxy does.


--]]--


 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 to TCP and back


We write two functions that will operate on two data transfer channels.


--]]--


 --    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 

--[[--


Both functions are executed immediately after startup by coroutine.yield (). This allows the first call to pass the parameters of the function and then do coroutine.resume (co) without additional parameters.


main


And now the main function that will prepare and run the main loop.


--]]--


 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 

--[[--


We start the main function. If the connection is suddenly closed, we will install it again after a second by calling main.


--]]--


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

check


  1. Run stunnel


  2. Run our script


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

  3. We check the script work with the command


     nslookup ya.ru 127.0.0.1 

    This time without `-vc ', we already wrote and launched a proxy that wraps UDP DNS requests in a TCP tunnel.



Result:


 *** 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 ) 

If everything is ok, you can specify it in the connection settings as the DNS server "127.0.0.1"


conclusion


Now our DNS queries are protected by TLS .


PS We do not give Google extra information about us


Immediately after connecting, Windows tries to register us with Google’s DNS servers through our tunnel. This is disabled in advanced DNS settings by unchecking.



There is another request for time.windows.com. He is no longer so personal, but I never found out how to turn it off. Automatic time synchronization is disabled.


links


  1. RFC1035: DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION
  2. DNS over TLS
  3. simple-udp-to-tcp-dns-proxy.lua
  4. Compile a DNS query manually

')

Source: https://habr.com/ru/post/427957/


All Articles