Несколько дней назад у Хавьера был простой сценарий оболочки, который он разместил в нашем внутреннем чате. Его цель состояла в том, чтобы получить все диапазоны IP-адресов для страны в рамках подготовки к отпечатку с https://ipinfo.io/ (в качестве примера возьмем PL). Учитывая, что это связано с извлечением нескольких веб-страниц, мне было интересно узнать, какой будет наиболее эффективный подход к этому в оболочке. Честно говоря, сама проблема, вытягивание данных с сайта или сбор BGP-маршрутов, меня не интересовала, я хотел посмотреть, как наиболее эффективно сделать массовый HTTP enum с помощью curl.
Как и в случае со всеми сценариями оболочки, его первоначальный подход имел некоторые… проблемы, и потребовалось более 4 часов, чтобы извлечь все данные, мы опустим эту версию и используем следующую версию, которую Хавьер, Роган и я придумали в качестве основы:
seq 1 3 \ | xargs -I% curl -s "https://ipinfo.io/countries/pl/%" \ | grep -oE "AS[0-9]{1,9}" \ | sort -u \ | xargs -I% curl -s "https://ipinfo.io/%" \ | grep -Eo '[0-9\.]{7,15}\/[0-9]{1,2}' \ | sort -u
Сценарий:
- Выбирает три страницы: https://ipinfo.io/countries/pl/1, https://ipinfo.io/countries/pl/2 и https://ipinfo.io/countries/pl/3. »
- Затем grep извлекает номера AS маршрутизации, например. АС5617
- У каждого из них есть свои данные, например. https://ipinfo.io/AS5617
- Затем они анализируются для адресов CIDR, например. 178.42.0.0/15.
Если вы не знакомы с xargs, это просто позволяет вам выполнить что-то через ввод, например:
> ls foo bar baz > file * foo: empty bar: empty baz: empty > ls | xargs file foo: empty bar: empty baz: empty > ls | xargs echo file file foo bar baz > ls | xargs -L1 echo file file foo file bar file baz
-L1 просто говорит выполнять его для каждого элемента, это необходимо понять позже.
Тестовая среда
Я не хотел продолжать извлекать сотни мегабайт данных из ipinfo, поэтому я вытащил все данные на свою машину и передал их с помощью http-сервера npm. Это означает, что я просто заменил ссылки на https://127.0.0.1:8080/. Это также означало, что я мог устранить несоответствия, появившиеся при перемещении данных через Интернет. В целом, 5 706 файлов представляли собой 276 МБ необработанных байтов. Подавляющее большинство из них были страницами AS, средний размер которых составлял 49 КБ, причем самый большой из них составлял 892 КБ, а самый маленький - 12 КБ.
Для записи выполнения скрипта я использовал утилиту времени Unix. Для записи сетевого трафика я использовал tshark. Я также использовал tshark для создания статистики с помощью сводки «-z conv,ip».
Итак, наш базовый прогон, выполненный на моем MBP, выглядит так, с важной сводкой в конце.
seq 1 3 0.00s user 0.00s system 44% cpu 0.010 total xargs -I% curl -s "https://127.0.0.1:8080/%" 0.02s user 0.02s system 40% cpu 0.108 total grep --color -oE "AS[0-9]{1,9}" 0.03s user 0.00s system 33% cpu 0.108 total sort -u 0.01s user 0.01s system 14% cpu 0.113 total xargs -I% curl -s "https://127.0.0.1:8080/%" 35.07s user 30.07s system 50% cpu 2:07.74 total grep --color -Eo '[0-9\.]{7,15}\/[0-9]{1,2}' 16.09s user 0.19s system 12% cpu 2:07.74 total sort -u 0.15s user 0.06s system 0% cpu 2:07.94 total 2:07.94 total time 94 764 frames 295 926 465 bytes
Вы можете видеть, что это заняло более 2 минут, сгенерировало 94k кадров и 282M данных.
Подход 1: параллелизм
Многократная обработка проблемы часто является самым простым способом ускорить что-то. Это можно просто сделать, передав переключатель -P в xargs выше. Использование произвольного значения 20 параллельных процессов дает следующий код:
time seq 1 3\ | xargs -P20 -I% curl -s "https://127.0.0.1:8080/%" \ | grep -oE "AS[0-9]{1,9}" \ | sort -u \ | xargs -P20 -I% curl -s "https://127.0.0.1:8080/%" \ | grep -Eo '[0-9\.]{7,15}\/[0-9]{1,2}' \ | sort -u
Это дает следующую статистику:
seq 1 3 0.00s user 0.00s system 46% cpu 0.009 total xargs -P20 -I% curl -s "https://127.0.0.1:8080/%" 0.03s user 0.03s system 83% cpu 0.068 total grep --color -oE "AS[0-9]{1,9}" 0.03s user 0.00s system 56% cpu 0.068 total sort -u 0.01s user 0.00s system 22% cpu 0.073 total xargs -P20 -I% curl -s "https://127.0.0.1:8080/%" 48.46s user 46.47s system 304% cpu 31.160 total grep --color -Eo '[0-9\.]{7,15}\/[0-9]{1,2}' 23.50s user 0.58s system 77% cpu 31.163 total sort -u 0.16s user 0.05s system 0% cpu 31.337 total 31.337 total time 94 777 frames 296 116 785 bytes
Отлично, это сокращает время до четверти исходного и не должно влиять на количество кадров или переданных байтов, но на самом деле этот материал немного менее детерминирован.
Подход 2: Конвейерная обработка
Большинство HTTP-серверов поддерживают конвейерную обработку, когда несколько запросов выполняются и обслуживаются в рамках одного и того же TCP-соединения. Это избавляет от необходимости каждый раз устанавливать и разрывать новое TCP-соединение.
Роган нашел изящный подход к конвейерной обработке с помощью curl с помощью параметра конфигурации, результирующий код выглядит следующим образом:
time curl -s --config \ <(for x in \ $(curl -s --config \ <(for i in `seq 1 3` do echo "url=https://127.0.0.1:8080/$i" done) \ | grep -oE "AS[0-9]{1,9}" \ | sort -u) do echo "url=https://127.0.0.1:8080/$x" done) \ | grep -Eo '[0-9\.]{7,15}\/[0-9]{1,2}' \ | sort -u
Это создает список url=… с символами новой строки между ними и передает его для скручивания в виде файла конфигурации с перенаправлением оболочки ‹.
Это немного менее читабельно, и большая часть времени выполнения скрыта в первом процессе curl, но давайте проверим, как он работает:
curl -s --config 0.69s user 0.60s system 9% cpu 14.336 total grep --color -Eo '[0-9\.]{7,15}\/[0-9]{1,2}' 13.97s user 0.10s system 98% cpu 14.339 total sort -u 0.14s user 0.05s system 1% cpu 14.522 total 14.522 total time 44 867 frames 292 995 529 bytes
Вау, это вдвое меньше времени и больше половины количества кадров по сравнению с многопроцессорной версией. Однако это всего на 1% меньше, если смотреть на общее количество байтов. Таким образом, дополнительные пакеты действительно создают большие накладные расходы на обработку, несмотря на то, что они не приводят к значительному увеличению объема данных.
Подход 3: Гибрид
Затем я хотел, чтобы мы могли объединить оба подхода, что привело бы к некоторым забавным сценариям. Вот результат:
for x in $(for i in $(seq 1 3) do echo "https://127.0.0.1:8080/$i" done \ | xargs -L1 -P3 curl -s \ | grep -oE "AS[0-9]{1,9}" \ | sort -u) do echo "https://127.0.0.1:8080/$x" done \ | xargs -L287 -P20 curl -s \ |grep -Eo '[0-9\.]{7,15}\/[0-9]{1,2}' \ | sort -u
Это не использует изящный трюк Рогана –config для curl, а просто передает URL-адреса в качестве входных данных и контролирует, сколько URL-адресов будет передано с помощью -L. Я думаю, что это немного более читабельно. Учитывая, что первый цикл извлекает три страницы, имеет смысл делать это одновременно, следовательно, -L1 -P3. Для последнего я взял 5 703 страницы, которые нужно было извлечь, и разделил их на 20 процессов из предыдущих, что дало мне 287.
Это дает следующие результаты:
for x in ; do; echo "https://127.0.0.1:8080/$x"; done 0.13s user 0.06s system 128% cpu 0.146 total xargs -L287 -P20 curl -s 0.83s user 1.16s system 13% cpu 14.541 total grep --color -Eo '[0-9\.]{7,15}\/[0-9]{1,2}' 13.64s user 0.28s system 95% cpu 14.543 total sort -u 0.13s user 0.05s system 1% cpu 14.704 total 14.704 total time 48 318 frames 293 187 882 bytes
Таким образом, это приводит к почти такой же производительности, как и конвейерная версия, с несколькими тысячами дополнительных кадров и несколькими сотнями дополнительных килобайт.
Вывод
+-------------+---------+----------+-----------+--------+ | Test | Base | Parallel | Pipelined | Hybrid | +-------------+---------+----------+-----------+--------+ | Time m:s.ms | 2:07.94 | 31.337 | 14.522 | 14.704 | | Frames | 94 764 | 94 777 | 44 867 | 48 318 | | Data M | 282.21 | 282.39 | 279.42 | 279.60 | +-------------+---------+----------+-----------+--------+
Если вам нужно сделать много небольших HTTP-запросов, используйте конвейерную обработку curl для максимальной скорости и пропускной способности. Если вам требуется быстрое ускорение для обычного сценария оболочки, xargs -P — ваш друг. Вы также можете использовать параллельную обработку (которая оказалась намного медленнее в этих тестах), что позволит вам выполнять работу на нескольких хостах. Надеюсь, вы также почерпнули немного шелл-скрипт-фу.
Первоначально опубликовано на странице https://sensepost.com/blog/2018/efficient-http-scripting-in-the-shell/