#include "ngx_event.h" #ifdef __clang__ #pragma clang diagnostic ignored "-Wdangling-else" #else #pragma GCC diagnostic ignored "-Wdangling-else" #pragma GCC diagnostic ignored "-Wmultistatement-macros" #endif #include "muninn.h" #include #include // Configuration functions static char *mun_conf_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *mun_conf_connection_ct(ngx_conf_t *cf, ngx_command_t *c, void *v); // Connection lifecycle static void mun_init_connection(ngx_connection_t *c); static void mun_close_connection(ngx_connection_t *c); static void mun_handle_conn_readable(ngx_event_t *ev); static void mun_handle_conn_writeable(ngx_event_t *ev); // Request lifecycle functions static ngx_int_t mun_log_request(mun_dns_request *r); // connection pool flag stored here static ngx_int_t mun_daemon_dns_connection_pool_ct = MUN_DEFAULT_CONN_POOL_SZ; // Module definitions static ngx_command_t ngx_muninn_daemon_commands[] = { { ngx_string("dns_listen"), NGX_MUNINN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE, mun_conf_listen, 0, 0, NULL }, { ngx_string("dns_connection_pool_count"), NGX_MUNINN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE, mun_conf_connection_ct, 0, 0, NULL }, ngx_null_command }; static ngx_core_module_t ngx_muninn_daemon_module_ctx = { ngx_string("muninn_daemon"), NULL, NULL }; ngx_module_t ngx_muninn_daemon_module = { NGX_MODULE_V1, &ngx_muninn_daemon_module_ctx, ngx_muninn_daemon_commands, NGX_MUNINN_MODULE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING }; // Muninn request handling state machine static ngx_int_t (*mun_read_handler_list[])(mun_dns_request *) = { mun_log_request, // test req state machine logging handler // TODO: request parsing // TODO: request logging // TODO: request cache lookup // TODO: request record(s) fetch // TODO: request response generation // TODO: error handling phase }; /* ADJACENCY LIST * This matrix defines the entire request processing state machine and changes * All numbers in this list should correspond to mun_read_handler_list indexes * The entries in this list are linked to their same index in the handler list * * Entries: * { NGX_OK next handler - intended to indidicate phase is complete * NGX_ERROR next handler - intended to trap error states * NGX_BUSY next handler - intended to trap waiting state * NGX_DECLINED next handler } intended to trap service refusal * * Other handler returns: * NGX_DONE: this return code means a request is ready to be finalized * nothing further will be returned. * NGX_AGAIN: this return code causes same function to be called again. * primarily used when more input needed than what is available. * NGX_ABORT: this return code causes all further action to stop for request * * For more information, see how this is used in mun_handle_conn_readable */ static ngx_uint_t mun_read_handler_adjacency[][4] = { {0, 0, 0, 0} }; static char *mun_conf_connection_ct( ngx_conf_t *cf, ngx_command_t *cmd, void *conf ) { ngx_int_t argc = cf->args->nelts; ngx_str_t *argv = cf->args->elts; ngx_int_t num; if (argc < 1) { ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "dns_connection_pool_count requires at least one argument"); return NGX_CONF_ERROR; } if ((num = ngx_atoi(argv[1].data, argv[1].len)) == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "invalid integer passed to dns_connection_pool_count"); return NGX_CONF_ERROR; } if (num < 1) { ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "dns_connection_pool_count must be set to at least 1"); return NGX_CONF_ERROR; } mun_daemon_dns_connection_pool_ct = num; return NGX_CONF_OK; } static char *mun_conf_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *in) { ngx_url_t *u; ngx_int_t argc; ngx_str_t *argv; ngx_listening_t *listening; argc = cf->args->nelts; argv = cf->args->elts; if (argc < 1) { ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "dns_listen needs at least one argument"); return NGX_CONF_ERROR; } if (!(u = ngx_pcalloc(cf->pool, sizeof(ngx_url_t)))) return NGX_CONF_ERROR; u->url = argv[1]; u->listen = 1; u->default_port = 53; if (ngx_parse_url(cf->pool, u) != NGX_OK) { if (u->err) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s in \"%V\" of the \"dns_listen\" directive", u->err, &u->url); } return NGX_CONF_ERROR; } listening = ngx_create_listening(cf, (struct sockaddr *) &u->sockaddr, u->socklen); if (!listening) return NGX_CONF_ERROR; listening->addr_ntop = 1; listening->pool_size = mun_daemon_dns_connection_pool_ct; listening->handler = mun_init_connection; listening->logp = cf->log; listening->log.data = &listening->addr_text; listening->log.handler = ngx_accept_log_error; listening->sndbuf = 1; listening->rcvbuf = 1; listening->backlog = mun_daemon_dns_connection_pool_ct * 2; listening->protocol = IPPROTO_UDP; listening->type = SOCK_DGRAM; // listener added to cycle at creation ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "muninn DNS server enabled"); return NGX_CONF_OK; } static void mun_init_connection(ngx_connection_t *c) { mun_dns_request *r; if (!(r = ngx_pcalloc(c->pool, sizeof(mun_dns_request)))) { mun_close_connection(c); return; } ngx_log_error(NGX_LOG_NOTICE, c->log, 0, "muninn: new request accepted"); c->data = r; c->log->connection = c->number; c->log_error = NGX_ERROR_INFO; c->read->handler = mun_handle_conn_readable; c->write->handler = mun_handle_conn_writeable; if (c->read->ready) { ngx_post_event(c->read, &ngx_posted_events); } else { if (ngx_handle_read_event(c->read, 0) != NGX_OK) { mun_close_connection(c); return; } } } static void mun_close_connection(ngx_connection_t *c) { c->destroyed = 1; ngx_close_connection(c); ngx_destroy_pool(c->pool); } static void mun_handle_conn_readable(ngx_event_t *ev) { ngx_connection_t *c; mun_dns_request *r; ngx_chain_t *cur; ngx_int_t ret, idx, space, filled; c = ev->data; r = c->data; ret = 0; filled = 0; if (r && r->finalized) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "muninn: read called for finalized request"); return; } if (ev->timedout) { // TODO: handle timedout according to DNS proto mun_close_connection(c); return; } if (!r->in) { if (!(r->in = ngx_alloc_chain_link(c->pool))) { ret = NGX_ERROR; goto content; } if (!(r->in->buf = ngx_create_temp_buf(c->pool, MUN_DNS_LEGACY_UDP_SZ))) { ret = NGX_ERROR; goto content; } cur = r->in; cur->buf->last_buf = 1; cur->next = NULL; space = MUN_DNS_LEGACY_UDP_SZ; } else { for (cur = r->in; cur && !cur->buf->last_buf; cur = cur->next) {} if (!cur) { ngx_log_error(NGX_LOG_ERR, r->conn->log, 0, "muninn: failed to find next buffer to read into"); } space = cur->buf->end - cur->buf->last; } if (!r->read_cur) { r->read_cur = r->in; } while (1) { filled = c->recv(c, cur->buf->last, space); if (c->read->error || c->read->eof) { ngx_log_error(NGX_LOG_ERR, c->log, ngx_errno, "muninn: recv() failed"); ret = NGX_ERROR; goto content; } if (filled == space) { cur->buf->last_buf = 0; if (!(cur = (cur->next = ngx_alloc_chain_link(c->pool))) || !(cur->buf = ngx_create_temp_buf(c->pool, MUN_DNS_LEGACY_UDP_SZ)) || !(space = MUN_DNS_LEGACY_UDP_SZ)) break; cur->next = NULL; cur->buf->last_buf = 1; continue; } break; } content: if (!ret) ret = mun_read_handler_list[r->read_state](r); idx = mun_read_handler_adjacency[r->read_state][0 - ret]; r->read_state = idx; switch (ret) { // Abort case: immediately cease all action on this request. case NGX_ABORT: mun_close_connection(c); return; // These cases mark the end of one phase of request processing. case NGX_ERROR: case NGX_BUSY: case NGX_DECLINED: case NGX_OK: // may still be more data to process in next phase ngx_post_event(ev, &ngx_posted_events); break; // This request is fully finished being processed. Time to write response. case NGX_DONE: r->finalized = 1; if (ngx_handle_write_event(c->write, 0) != NGX_OK) { mun_close_connection(c); } if (c->write->ready) ngx_post_event(c->write, &ngx_posted_events); /* fallthrough */ // Need more data for current phase. case NGX_AGAIN: if (ngx_handle_read_event(ev, 0) != NGX_OK) { mun_close_connection(c); } break; } } static void mun_handle_conn_writeable(ngx_event_t *ev) { ngx_chain_t *tmp; ngx_connection_t *c; mun_dns_request *r; c = ev->data; r = c->data; tmp = r->out; /* this event posted for one of two reasons: * 1. mun_handle_request_read has reached end of request processing * 2. socket newly available after incomplete write */ // write wouldnt work here anyways. event will retrigger when ready if (!ev->ready) return; if (!r->out) { ngx_log_debug(NGX_LOG_ALERT, r->conn->log, 0, "muninn: request processed but no output"); return; } r->out = c->send_chain(c, tmp, MUN_DNS_LEGACY_UDP_SZ); for (/* tmp */; tmp != r->out; tmp = tmp->next) { if (tmp->buf->pos == tmp->buf->last) ngx_free_chain(c->pool, tmp); } // request lifecycle complete condition if (!r->out) return mun_close_connection(c); // event will retrigger when write possible if (!ev->ready) return; // more can be written but we want to yield to other events on task loop ngx_post_event(ev, &ngx_posted_events); } static ngx_int_t mun_log_request(mun_dns_request *r) { ngx_log_error(NGX_LOG_INFO, r->conn->log, 0, "muninn: request state machine entered"); return NGX_DONE; }