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:
And in the opposite direction:
openssl x509 -inform DER -in Root-R2.crt -out Root-R2.pem -text
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:
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 )
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
--[[--
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.
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
Run stunnel
Run our script
lua5.3 simple-udp-to-tcp-dns-proxy.lua
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"
Now our DNS queries are protected by TLS .
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.
Source: https://habr.com/ru/post/427957/
All Articles