- PVSM.RU - https://www.pvsm.ru -
[1]
источник изображения [2]
После новости о том что "Google Public DNS тихо включили поддержку DNS over TLS" [3] я решил попробовать его. У меня уже есть Stunnel [4] который создаст шифрованный TCP [5] туннель до гугла. Но программы обычно общаются с DNS [6] по UDP [7] протоколу. Поэтому нам нужен прокси который будет пересылать UDP пакеты в TCP поток и обратно. Мы напишем его на Lua [8].
Вся разница между TCP и UDP DNS пакетами:
4.2.2. TCP usage
Messages sent over TCP connections use server port 53 (decimal). The message is prefixed with a two byte length field which gives the message length, excluding the two byte length field. This length field allows the low-level processing to assemble a complete message before beginning to parse it.
RFC1035: DOMAIN NAMES — IMPLEMENTATION AND SPECIFICATION [9]
То есть делаем туда:
И в обратную сторону:
Настройка простая. Пишем в stunnel.conf:
[dns]
client = yes
accept = 127.0.0.1:53
connect = 8.8.8.8:853
То есть Stunnel:
Запускаем Stunnel
Работу тунеля можно проверить командой:
nslookup -vc ya.ru 127.0.0.1
Опция vc заставляет nslookup использовать TCP соединение к DNS серверу вместо UDP.
Результат:
*** 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 [10]. В нём уже доступны бинарные операции с числами. Ну и нам понадобится модуль Lua Socket [11].
Имя файла: simple-udp-to-tcp-dns-proxy.lua [12]
local socket = require "socket" -- подключаем lua socket
--[[--
Напишем простенькую функцию которая позволит отправить дамп пакета в консоль. Хочется видеть что делает прокси.
--]]--
function serialize(data)
-- Преобразуем символы не входящие в диапазоны a-z и 0-9 и тире в HEX представление 'xFF'
return "'"..data:gsub("[^a-z0-9-]", function(chr) return ("\x%02X"):format(chr:byte()) end).."'"
end
--[[--
Пишем две функции которые будут оперировать двумя каналами передачи данных.
--]]--
-- здесь пакеты из 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 пакета
clients[id] = {ip=err_ip, port=port} -- записываем адрес отправителя
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 пакета
local client = clients[id] -- находим получателя
if client then
udp_out:sendto(packet, client.ip, client.port) -- отправляем пакет получателю по UDP
clients[id] = nil -- очищаем ячейку
print(os.date("%c", os.time()) ,client.ip, client.port, "<", serialize(packet)) -- отображаем пакет в консоль
end
until false
end
--[[--
Обе функции сразу после запуска выполняют coroutine.yield(). Это позволяет первым вызовом передать параметры функции а дальше делать coroutine.resume(co) без дополнительных параметров.
А теперь main функция которая выполнит подготовку и запустит главный цикл.
--]]--
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
--[[--
Запускаем главную функцию. Если вдруг будет закрыто соединение мы через секунду установим его заново вызвав main.
--]]--
repeat
coroutine.resume(coroutine.create(main)) -- запускаем main
socket.sleep(1) -- перед рестартом ждём одну секунду
until false
Запускаем stunnel
Запускаем наш скрипт
lua5.3 simple-udp-to-tcp-dns-proxy.lua
Проверяем работу скрипта командой
nslookup ya.ru 127.0.0.1
На этот раз без '-vc' так так мы уже написали и запустили прокси который заворачивает 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 яндекса)
Если всё нормально можно указать в настройках соедидения как DNS сервер "127.0.0.1"
Теперь наши DNS запросы под зашитой TLS [13].
Автор: ivan386
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/stunnel/297207
Ссылки в тексте:
[1] Image: https://habr.com/post/427957/
[2] источник изображения: https://commons.wikimedia.org/wiki/File:%D0%94%D0%9E%D0%A2_%E2%84%96401_(%D0%BD%D0%B0%D0%B4%D0%B7%D0%B5%D0%BC%D0%BD%D0%B0_%D1%87%D0%B0%D1%81%D1%82%D0%B8%D0%BD%D0%B0).jpg
[3] "Google Public DNS тихо включили поддержку DNS over TLS": https://habr.com/post/427639/
[4] Stunnel: https://ru.wikipedia.org/wiki/Stunnel
[5] TCP: https://ru.wikipedia.org/wiki/Transmission_Control_Protocol
[6] DNS: https://ru.wikipedia.org/wiki/DNS
[7] UDP: https://ru.wikipedia.org/wiki/UDP
[8] Lua: https://ru.wikipedia.org/wiki/Lua
[9] RFC1035: DOMAIN NAMES — IMPLEMENTATION AND SPECIFICATION: https://www.ietf.org/rfc/rfc1035
[10] Lua 5.3: https://www.lua.org/
[11] Lua Socket: http://w3.impa.br/~diego/software/luasocket/
[12] simple-udp-to-tcp-dns-proxy.lua: https://github.com/ivan386/lua-simple-udp-to-tcp-dns-proxy/blob/master/simple-udp-to-tcp-dns-proxy.lua
[13] TLS: https://ru.wikipedia.org/wiki/TLS
[14] DNS поверх TLS: https://ru.wikipedia.org/wiki/DNS_%D0%BF%D0%BE%D0%B2%D0%B5%D1%80%D1%85_TLS
[15] Источник: https://habr.com/post/427957/?utm_source=habrahabr&utm_medium=rss&utm_campaign=427957
Нажмите здесь для печати.