nginxучастия я никогда не принимал, так как мой навык работы в Си находится где-то на уровне 1/10. Однако меня не страшит идея скачать исходный код, разобрать его, скомпилировать и запустить. Цель этой статьи помочь и вам преодолеть собственный страх проделать то же самое.
И дело не в том, что вам стоит выполнять в продакшене собственные ответвления программы, а в том, что я вижу многих разработчиков, которых даже не посещала идея познакомиться с исходным кодом привычного им серьезного инструмента или зависимости.
Самое же главное, что изучение зрелых проектов является одним из лучших способов совершенствования навыков программирования.
Исходник и сборка
На верхнем уровне этапы хакинга программных проектов всегда одинаковы:
- Найти/скачать исходный код.
- Установить необходимые библиотеки/компиляторы.
- Начать с
grep’инга чего-то, наблюдаемого в выводе, или известных вам возможностей программы. - Внести изменения.
- Выполнить вариацию
./configure && makeдля сборки. - Запустить программу.
- Возвращаться к шагу 4, пока не получите желаемый результат.
nginx
Давайте проделаем все эти шаги для nginx. Через поиск в Google по запросу nginx github находим досутпную только для чтения версию исходного кода на GitHub.
$ mkdir ~/vendor $ cd ~/vendor $ git clone https://github.com/nginx/nginx $ cd nginx
Облом, здесь нет readme. Снова идем в Google, но теперь с запросом nginx build from source, и находим это.
Тут мы наблюдаем типичный проект Си, который собирается вполне ожидаемым образом: ./configure && make. При этом не похоже, чтобы у него были какие-то сторонние зависимости, кроме моего компилятора Си.
Устанавливаем autoconf, gmake и компилятор Си. В этом каталоге нет файла ./configure, но заметьте, что он есть в auto. Попытка выполнить cd auto && ./configure проходит безуспешно, так что попробуем ./auto/configure. Вроде сработало, но вызвало предупреждение:
$ ./auto/configure ... ./auto/configure: error: the HTTP rewrite module requires the PCRE library. You can either disable the module by using --without-http_rewrite_module option, or install the PCRE library into the system, or build the PCRE library statically from the source with nginx by using --with-pcre=<path> option.
Выполняем ./auto/configure --without-http_rewrite_module и потом еще раз, когда она дает сбой, но уже без http_gzip_module.
Отлично, автонастройка выполнена. Теперь у нас есть Makefile. Выполняем make -j для компиляции с использованием всех ядер.
Далее выполняем git status, чтобы увидеть расположение бинарника. Теперь ls objs и… вуаля:
$ ls objs autoconf.err nginx ngx_auto_config.h ngx_modules.c src Makefile nginx.8 ngx_auto_headers.h ngx_modules.o
Хак
Нам нужна простая команда dump, которая будет возвращать строковый литерал в блоке location. Что-то вроде этого:
$ diff --git a/conf/nginx.conf b/conf/nginx.conf index 29bc085f..e96e817f 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -41,8 +41,7 @@ http { #access_log logs/host.access.log main; location / { - root html; - index index.html index.htm; + dump 'It was a good Thursday.'; } #error_page 404 /404.html; }
Теперь, собрав nginx, можно использовать флаг -t для проверки валидности этой конфигурации:
$ ./objs/nginx -t -c $(pwd)/conf/nginx.conf nginx: [alert] could not open error log file: open() "/usr/local/nginx/logs/error.log" failed (2: No such file or directory) 2021/04/04 21:24:09 [emerg] 1030951#0: unknown directive "dump" in /home/phil/vendor/nginx/conf/nginx.conf:44 nginx: configuration file /home/phil/vendor/nginx/conf/nginx.conf test failed
Вот теперь у нас есть от чего оттолкнуться! Очевидно, что нам нужно зарегистрировать эту директиву, и эта запись дает достаточно информации для начала grep-инга:
$ git --no-pager grep 'unknown directive' src/core/ngx_conf_file.c: "unknown directive \"%s\"", name->data);
Кейс, который содержит этот сбой, находится на строчке 463: rv = cmd->set(cf, cmd, conf). Посмотрим, что делает set. Команда git grep set здесь не поможет. Так что попробуем выяснить, что такое cmd, чтобы можно было найти структуру, содержащую set.
Ага – это ngx_command_t. Поскольку перед ней нет struct, это означает, что определена она с помощью typedef и скорее всего завершается на ;. Итак, git grep ngx_command_t\; дает:
$ git --no-pager grep ngx_command_t\; src/core/ngx_core.h:typedef struct ngx_command_s ngx_command_t;
И это значит, что реализация скрыта. Тогда ищем ngx_command_s:
$ git --no-pager grep ngx_command_s src/core/ngx_conf_file.h:struct ngx_command_s { src/core/ngx_core.h:typedef struct ngx_command_s ngx_command_t;
Ладно, это ни к чему не ведет. Меняем подход. Посмотрим, какую же команду мы удалили.
$ git --no-pager diff diff --git a/conf/nginx.conf b/conf/nginx.conf index 29bc085f..e96e817f 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -41,8 +41,7 @@ http { #access_log logs/host.access.log main; location / { - root html; - index index.html index.htm; + dump 'It was a good Thursday.'; } #error_page 404 /404.html;
root является командой. Попробуем ее скопировать.
$ git --no-pager grep \"root\" docs/xml/nginx/changes.xml:in the "root" or "auth_basic_user_file" directives. docs/xml/nginx/changes.xml:a request was handled incorrectly, if a "root" directive used variables; docs/xml/nginx/changes.xml:the $document_root variable usage in the "root" and "alias" directives docs/xml/nginx/changes.xml:the $document_root variable did not support the variables in the "root" docs/xml/nginx/changes.xml:if a "root" was specified by variable only, then the root was relative src/http/ngx_http_core_module.c: { ngx_string("root"), src/http/ngx_http_core_module.c: &cmd->name, clcf->alias ? "alias" : "root");
Это уже интереснее. Скопируем:
$ git --no-pager diff src/http/ diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 9b94b328..17a64e80 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -331,6 +331,14 @@ static ngx_command_t ngx_http_core_commands[] = { 0, NULL }, + { ngx_string("dump"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF + |NGX_CONF_TAKE1, + ngx_http_core_dump, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + { ngx_string("alias"), NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_core_root,
Ясно. Значит, вот как регистрируется команда. Очевидно, что сборку без ngx_http_core_dump мы не сделаем, так что давайте реализуем ее, скопировав/переименовав ngx_http_core_root:
$ git --no-pager diff src diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 9b94b328..c184dab5 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4402,6 +4410,16 @@ ngx_http_core_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +static char * +ngx_http_core_dump(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf = conf; + ngx_str_t *value = cf->args->elts; + clcf->dump = value[1]; + return NGX_CONF_OK; +} + + static ngx_http_method_name_t ngx_methods_names[] = { { (u_char *) "GET", (uint32_t) ~NGX_HTTP_GET }, { (u_char *) "HEAD", (uint32_t) ~NGX_HTTP_HEAD },
Здесь наша цель просто сохранить строку дампа в этом объекте conf. Затем в процессе обработки запроса мы сможем проверить, устанавливается ли она, и если да, то ответить на запрос этой строкой.
Понятно, что этот код по-прежнему не соберется, так как мы не изменили объект conf. Но make мы все же выполним:
$ make -f objs/Makefile make[1]: Entering directory '/home/phil/vendor/nginx' cc -c -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules \ -o objs/src/http/ngx_http_core_module.o \ src/http/ngx_http_core_module.c src/http/ngx_http_core_module.c:337:7: error: ngx_http_core_dump undeclared here (not in a function); did you mean ngx_http_core_type? 337 | ngx_http_core_dump, | ^~~~~~~~~~~~~~~~~~~~~ | ngx_http_core_type src/http/ngx_http_core_module.c: In function ngx_http_core_dump: src/http/ngx_http_core_module.c:4418:9: error: ngx_http_core_loc_conf_t {aka struct ngx_http_core_loc_conf_s} has no member named dump 4418 | clcf->dump = value[1]; | ^~ src/http/ngx_http_core_module.c:4418:5: error: statement with no effect [-Werror=unused-value] 4418 | clcf->dump = value[1]; | ^~~~ At top level: src/http/ngx_http_core_module.c:4414:1: error: ngx_http_core_dump defined but not used [-Werror=unused-function] 4414 | ngx_http_core_dump(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | ^~~~~~~~~~~~~~~~~~~~~ cc1: all warnings being treated as errors make[1]: *** [objs/Makefile:834: objs/src/http/ngx_http_core_module.o] Error 1 make[1]: Leaving directory '/home/phil/vendor/nginx' make: *** [Makefile:10: build] Error 2
Обработчик дампа не объявлен. Когда я копировал ngx_http_core_root, то выше видел предварительное объявление. Давайте его тоже скопируем и посмотрим, поможет ли.
$ git --no-pager diff diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 9b94b328..430e1256 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -56,6 +56,7 @@ static char *ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, static char *ngx_http_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_core_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_core_dump(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_core_limit_except(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_core_set_aio(ngx_conf_t *cf, ngx_command_t *cmd,
Теперь сборка:
$ make make -f objs/Makefile make[1]: Entering directory '/home/phil/vendor/nginx' cc -c -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules \ -o objs/src/http/ngx_http_core_module.o \ src/http/ngx_http_core_module.c src/http/ngx_http_core_module.c: In function ngx_http_core_dump: src/http/ngx_http_core_module.c:4419:9: error: ngx_http_core_loc_conf_t {aka struct ngx_http_core_loc_conf_s} has no member named dump 4419 | clcf->dump = value[1]; | ^~ make[1]: *** [objs/Makefile:834: objs/src/http/ngx_http_core_module.o] Error 1 make[1]: Leaving directory '/home/phil/vendor/nginx' make: *** [Makefile:10: build] Error 2
Отлично. Теперь добавим dump в этот объект conf.
$ git --no-pager grep ngx_http_core_loc_conf_t\; src/http/ngx_http_core_module.h:typedef struct ngx_http_core_loc_conf_s ngx_http_core_loc_conf_t;
Далее просто клонируем root:
$ diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 2aadae7f..6b1b178b 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -333,6 +333,7 @@ struct ngx_http_core_loc_conf_s { /* location name length for inclusive location with inherited alias */ size_t alias; ngx_str_t root; /* root, alias */ + ngx_str_t dump; ngx_str_t post_action; ngx_array_t *root_lengths;
Выполняем make, и все проходит успешно!
Теперь проведем несколько часов за поиском удачного места для добавления хука в процессе запроса.
В конечном итоге на роль такого места, похоже, подходит ngx_http_core_find_config_phase, так как только в этом случае мы будем работать со структурой, в которую добавили dump.
Следующим шагом нужно выяснить, как отправить ответ. Поиск response с помощью grep здесь не особо поможет, как и использование write. Однако send обладает некоторым низкоуровневым, но при этом наглядным поведением.
$ git --no-pager grep send\( src/mail/ngx_mail.h:void ngx_mail_send(ngx_event_t *wev); src/mail/ngx_mail_auth_http_module.c: n = ngx_send(c, ctx->request->pos, size);) ...
Второй результат выглядит обещающе. Судя по этому файлу, я думаю, что нам нужен объект, содержащий ->data. Ранее в src/http/ngx_http_core_module.c я заметил, что объект запроса содержит интересный элемент: r->connection->write->data. Исходя из его сигнатуры, нужно просто также передать в ngx_send строку и длину.
Хорошо. Эти данные у нас уже есть из элемента dump, так что пробуем простой вариант:
$ git --no-pager diff diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 9b94b328..bd58788b 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -989,6 +996,11 @@ ngx_http_core_find_config_phase(ngx_http_request_t *r, ngx_http_finalize_request(r, NGX_HTTP_REQUEST_ENTITY_TOO_LARGE); return NGX_OK; } + + if (clcf->dump.len) { + ngx_send(r->connection->write->data, clcf->dump.data, clcf->dump.len); + return NGX_OK; + }
Выполняем make, и все проходит отлично! Давайте отключим демона nginx и процессы воркеров, чтобы упростить выход программы в течение наших экспериментов.
$ git --no-pager diff conf/ diff --git a/conf/nginx.conf b/conf/nginx.conf index 29bc085f..7cce7d65 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -1,4 +1,5 @@ - +daemon off; +master_process off; #user nobody; worker_processes 1;
Теперь выполняем ./objs/nginx -c $(pwd)/conf/nginx.conf. Пробуем curl:
$ curl localhost:2020 curl: (1) Received HTTP/0.9 when not allowed
А вот это неожиданно. Попробуем получить весь необработанный ответ с помощью telnet:
$ telnet localhost 2020 Trying ::1... telnet: connect to address ::1: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. GET / It was a good Thursday.
Вот это да. Супер круто! К сожалению, это тоже не валидный HTTP. Похоже, если мы используем ngx_send, то заголовки HTTP-ответа нужно устанавливать вручную.
Если мы собираемся передать в ngx_send строковый литерал, то нужно преобразовать его в ngx_str_t. Судя по src/core/ngx_string.h, с этим должен справиться макрос ngx_string.
$ git --no-pager diff src diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 9b94b328..1a1baccd 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -989,6 +996,13 @@ ngx_http_core_find_config_phase(ngx_http_request_t *r, ngx_http_finalize_request(r, NGX_HTTP_REQUEST_ENTITY_TOO_LARGE); return NGX_OK; } + + static ngx_str_t header = ngx_string("HTTP/1.0 200 OK\r\n\r\n"); + if (clcf->dump.len) { + ngx_send(r->connection->write->data, header.data, header.len); + ngx_send(r->connection->write->data, clcf->dump.data, clcf->dump.len); + return NGX_OK; + } if (rc == NGX_DONE) { ngx_http_clear_location(r); }
Компилируем, запускаем и выполняем curl:
$ curl localhost:2020
Мда. Программа больше не ругается на HTTP/0.9, зато теперь зависает. Попробуем расширенную версию curl:
$ curl -vvv localhost:2020 * Trying ::1:2020... * connect to ::1 port 2020 failed: Connection refused * Trying 127.0.0.1:2020... * Connected to localhost (127.0.0.1) port 2020 (#0) > GET / HTTP/1.1 > Host: localhost:2020 > User-Agent: curl/7.71.1 > Accept: */* > * Mark bundle as not supporting multiuse * HTTP 1.0, assume close after body < HTTP/1.0 200 OK
Очень странно. Но я заметил там функцию ngx_http_request_finalize, вызываемую из других участков кода. Попробуем ее добавить.
$ git --no-pager diff src diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 9b94b328..1a1baccd 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -989,6 +996,14 @@ ngx_http_core_find_config_phase(ngx_http_request_t *r, ngx_http_finalize_request(r, NGX_HTTP_REQUEST_ENTITY_TOO_LARGE); return NGX_OK; } + + static ngx_str_t header = ngx_string("HTTP/1.0 200 OK\r\n\r\n"); + if (clcf->dump.len) { + ngx_send(r->connection->write->data, header.data, header.len); + ngx_send(r->connection->write->data, clcf->dump.data, clcf->dump.len); + ngx_http_finalize_request(r, NGX_DONE); + return NGX_OK; + }
Собираем, запускаем, выполняем curl. Опять зависание. Если взглянуть на исходный код ngx_http_finalize_request, то похоже, что там есть кейс, в котором соединение полностью закрывается при передаче NGX_HTTP_CLOSE. Попробуем его.
$ curl localhost:2020 It was a good Thursday.
Ну вот. Сработало.
Что я из этого понял
Хороший ли это способ реализации команд в nginx? Нет. Несмотря на то, что я кое-что знал о модулях nginx на уровне пользователя, на уровне разработчика эта команду, как и модуль, можно было реализовать гораздо грамотнее.
При этом также необходимы высокоуровневые инструменты, чтобы возвращать создаваемые ответы, а не вводить заголовки вручную.
ссылка на оригинал статьи https://habr.com/ru/articles/575574/

Добавить комментарий