Через фильтрующий прокси при помощи скрипта во имя Луны

    Данная статья является лиш исследованием на тему и не должна использоваться как инструкция к действию.

    Блокировки продолжаются а я всё также не приветствую отдачу не шифрованного HTTP трафика в заморские прокси, Tor, анонимайзеры и включение экономии трафика в браузере. Пока есть возможность я буду пытаться ходить на сайты прямо. Заодно скорость связи с сайтом не будет зависеть от загруженности стороннего сервиса.

    Я поставил на Firefox плагин RequestPolicy и обнаружил в HTTP хедерах на сайте.
    X-Squid-Error:"403 Access Denied"
    

    Это значит что соединение к сайту проходит через прозрачный прокси.

    В этой статье я попробую пройти при помощи локального прокси написанного на Lua (Вики)

    Скачав LuaSocket 2.0.2 я написал небольшой скрипт локального прокси.

    require "socket"
    
    local cors = {}
    function main()
    	local proxy_bind = socket.tcp()
    	proxy_bind:setoption("reuseaddr", true) -- На всякий случай
    	proxy_bind:bind("localhost", 8080) -- Принимаем подключения только от локального клиента
    	proxy_bind:listen(100) -- 100 подключений в очереди
    	proxy_bind:settimeout(0) -- 0s Позволяет не задерживаться ожидая данных и забрать из буфера сразу всё.
    	repeat -- Главный цикл который принимает подключения
    		local client, accept_err = proxy_bind:accept()
    		if (client) then
    			local new_cor = coroutine.create(new_client)
    			local ok, err = coroutine.resume(new_cor, client)
    			if (ok) then
    				table.insert(cors, new_cor)
    			end
    		end
    		local new_list = {}
    		for id, cor in ipairs(cors) do
    			local ok, err = coroutine.resume(cor)
    			if (ok) then
    				table.insert(new_list, cor)
    			end
    		end
    		cors = new_list
    		socket.sleep(0.001) -- Чтоб не сильно нагружать процессор
    	until accept_err and (accept_err ~= "timeout")
    end
    

    -- Читаем хедеры и соединяемся с конечным сайтом сквозь фильтрующий прокси
    function new_client(client)
    	client:settimeout(0)
    	local headers, err = get_data(client:receive("*a"))
    	local host, port = get_host_port(headers)
    	headers = headers:gsub("( HTTP/1.1)\13(\10Host: )", "%1%2")
    	local server = socket.connect(host, port)
    	print("NC", host, port)
    	if (server) then
    		if (not send_connect(server, string.format("%s:%s", socket.dns.toip(host), port))) then
    			server:close()
    			server = socket.connect(host, port)
    		end
    		if (server) then
    			headers = headers:gsub("\10Connection%: keep%-alive\13\10", "\10Connection: close\13\10")
    			server:send(headers)
    			cycle_data(server, client, host)
    		end
    	end
    	print("CLOSED", host, port)
    	server:close()
    	client:close()
    end
    

    function get_data(data, err, part, marker)
    	data = (data or part)
    	return data, err
    end
    

    function get_host_port(headers)
    	if headers and (#headers > 0) then
    		if not (headers:find(" HTTP/1.1\13\10Host: ", 1, true)) then
    			return
    		end
    		local _, _, host, port = headers:find(" HTTP/1.1\13\10Host: ([a-z0-9%.%-]+):?([0-9]*)\13\10")
    		if (#port > 0) then
    			port = tonumber(port)
    		else
    			port = 80
    		end
    		return host, port
    	end
    end
    

    -- Используем метод CONNECT который превращает фильтрующий прокси в TCP трубу
    local connect = "CONNECT %s HTTP/1.1\13\10Host: %s\13\10\13\10"
    function send_connect(server, address)
    	server:send(string.format(connect, address, address))
    	server:settimeout(0.1)
    	local headers, err = get_data(server:receive("*a"))
    	while ((not headers) or (#headers <= 12)) and not (err and (err ~= "timeout")) do
    		coroutine.yield() -- Даём скрипту обработать другие подключения
    		local data, err = get_data(server:receive("*a"))
    		if (data and #data > 0) then
    			headers = (headers or "") .. data
    		end
    	end
    	return headers:find("^HTTP/1.[01] 200")
    end
    

    -- Пересылаем данные в обе стороны
    
    function cycle_data(server, client, host, port)
    	local _in_, out = server, client
    	repeat
    		_in_:settimeout(0)
    		out:settimeout(1)
    		
    		local data, receive_err = get_data(_in_:receive("*a"))
    		
    		if data and (#data > 0) then
    			data = data:gsub("\13\10Connection%: keep%-alive\13\10", "\13\10Connection: close\13\10")
    			local index, send_err = send_data(out, data)
    		end
    		
    		coroutine.yield() -- Даём скрипту обработать другие подключения
    		
    	until (receive_err and (receive_err ~= "timeout")) or (send_err and (send_err ~= "timeout"))
    end
    

    function send_data(out, data)
    	local index = 0
    	repeat
    		index = index +1
    		index, err = get_index(out:send(data, index))
    	until index >= #data or (err and (err ~= "timeout"))
    	
    	return index, err
    end
    
    function get_index(index, err, partial_index)
    	return (index or partial_index), err
    end
    
    main()
    

    Сохраняем скрипт как «proxy.lua» в распакованную папку с LuaSocket 2.0.2.

    Создаём простой «proxy.bat» файл для запуска «proxy.lua», который сохраним там же.
    %~d0
    cd %~p0
    lua5.1 proxy.lua
    pause
    


    Теперь подредактируем «proxy.pac» из прошлой статьи. (Адреса изменены)
    function FindProxyForURL(url, host) {
    
      if (shExpMatch(url, "http://*") && shExpMatch(host, "rutracker.og")) {
        return "PROXY localhost:8080; PROXY rutracker.og; DIRECT";
      }
    
      /* 
      Для HTTPS соединений через HTTP прокси метод CONNECT используется автоматически
      */
    
      if (shExpMatch(url, "https://*") && shExpMatch(host, "rutracker.og")) {
        return "PROXY rutracker.og; DIRECT";
      }
    
      return "DIRECT";
    }
    


    Запускаем прокси двойным кликом по «proxy.bat». Перезапускаем браузер и открываем rutracker.og.

    Скрипт писался быстро и чинился костылями. Это не полноценный прокси.
    Если кто придумает решение проще я буду очень рад.

    Дополнительно:
    На случай возможного разделегирования домена в файл hosts добавим строку (Адреса изменены):
    #198.51.100.0       rutracker.og
    

    И если домен разделегируют можно раскоментировать строчку удалив "#" и вернуть доступ к серверу.
    198.51.100.0       rutracker.og
    


    Так например можно вернуть доступ к серверу rutor.og (адрес изменён) взяв его ip из реестра или других источников.

    Использована информация:
    Lua 5.1 Reference Manual
    LuaSocket: TCP/IP support
    Дурим DPI двумя скриптами
    Proxy Auto Configuration (PAC)
    hosts — Википедия
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 10
    • 0
      Получается, что провайдер фильтрует GET и не фильтрует CONNECT на заблокированный адрес?
      • +2
        Да
        • 0
          После этой статьи будет и CONNECT фильтровать)
          • +2
            А зачем? задача провайдера — показать всем, что он блокировку произвёл. А проверять его будут всегда одним способом — ввести адрес и нажать Enter. И это выполнит именно GET-запрос.
            • 0
              Ну это русское «Авось» так работает. А если проверятор без «Авоськи» придет?
              • +1
                Судя по общей компетенции этих товарищей — никогда.
                • 0
                  Не. Ну это понятно))) Но вообще мало ли. Я к тому что вдруг что то поменяется)
                • +1
                  Тогда провайдеру дешевле всего будет закрыть клиентам доступ ко всему интернету. А то, мало ли, найдется очередной незабаненный прокси, да еще на каком-нибудь ipv6-адресе, да еще с неизвестным шифрованием канала от клиента до прокси, а «проверятель без авоськи» возьмет, да и сможет до злобного забаненного сайта достучаться? Проще все адреса всего инета заблочить, от греха, а то назовут пособником (подставьте-злобное-слово-из-новояза) и навечно клеймо повиснет, что провайдер — неблагонадежный.

                  А что провайдер имеет лицензию на передачу, а не на фильтрацию, данных, и что непереданный правильно информационный пакет как бы должен, при наличии жалобы от юзера, вызывать отзыв лицензии у провайдера, у «господ» (товарищей, правильно будет сказать) из этих, «я-знаю-как-все-должны-жить-правильно-а-про-мою-личную-жизнь-не-расскажу» контор, голова не болит.

                  Правда, если собрать факты блокировки вполне легетимного сайта (который имел неудачу висеть на одном IP с сайтом неблагонадежным) со стороны, скажем, РТ, да написать много личных жалоб в Роскомнадзор (мол, я, имярек, оплачивая план _доступа_ в сеть Интернет у провайдера РТ, не могу получить доступ к ресурсу такому-то, что нарушает, по моему мнению, условия лицензии..." — каков будет итог? )
                • 0
                  Вроде собирались ставить железки для тестирования работы цензуры и если на них этот трюк не сработает, то CONNECT тоже начнут блокировать.
                  • 0
                    Железки говорят стоят. Эти железки врятли будут проверять такое поведение потому что оно очень не стандартное и зависит от типа блокировки.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.