initial commit
This commit is contained in:
@@ -0,0 +1,561 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
#if (NGX_TEST_BUILD_DEVPOLL)
|
||||
|
||||
/* Solaris declarations */
|
||||
|
||||
#ifndef POLLREMOVE
|
||||
#define POLLREMOVE 0x0800
|
||||
#endif
|
||||
#define DP_POLL 0xD001
|
||||
#define DP_ISPOLLED 0xD002
|
||||
|
||||
struct dvpoll {
|
||||
struct pollfd *dp_fds;
|
||||
int dp_nfds;
|
||||
int dp_timeout;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t changes;
|
||||
ngx_uint_t events;
|
||||
} ngx_devpoll_conf_t;
|
||||
|
||||
|
||||
static ngx_int_t ngx_devpoll_init(ngx_cycle_t *cycle, ngx_msec_t timer);
|
||||
static void ngx_devpoll_done(ngx_cycle_t *cycle);
|
||||
static ngx_int_t ngx_devpoll_add_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_devpoll_del_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_devpoll_set_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_devpoll_process_events(ngx_cycle_t *cycle,
|
||||
ngx_msec_t timer, ngx_uint_t flags);
|
||||
|
||||
static void *ngx_devpoll_create_conf(ngx_cycle_t *cycle);
|
||||
static char *ngx_devpoll_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
static int dp = -1;
|
||||
static struct pollfd *change_list, *event_list;
|
||||
static ngx_uint_t nchanges, max_changes, nevents;
|
||||
|
||||
static ngx_event_t **change_index;
|
||||
|
||||
|
||||
static ngx_str_t devpoll_name = ngx_string("/dev/poll");
|
||||
|
||||
static ngx_command_t ngx_devpoll_commands[] = {
|
||||
|
||||
{ ngx_string("devpoll_changes"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
0,
|
||||
offsetof(ngx_devpoll_conf_t, changes),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("devpoll_events"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
0,
|
||||
offsetof(ngx_devpoll_conf_t, events),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_event_module_t ngx_devpoll_module_ctx = {
|
||||
&devpoll_name,
|
||||
ngx_devpoll_create_conf, /* create configuration */
|
||||
ngx_devpoll_init_conf, /* init configuration */
|
||||
|
||||
{
|
||||
ngx_devpoll_add_event, /* add an event */
|
||||
ngx_devpoll_del_event, /* delete an event */
|
||||
ngx_devpoll_add_event, /* enable an event */
|
||||
ngx_devpoll_del_event, /* disable an event */
|
||||
NULL, /* add an connection */
|
||||
NULL, /* delete an connection */
|
||||
NULL, /* trigger a notify */
|
||||
ngx_devpoll_process_events, /* process the events */
|
||||
ngx_devpoll_init, /* init the events */
|
||||
ngx_devpoll_done, /* done the events */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ngx_module_t ngx_devpoll_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_devpoll_module_ctx, /* module context */
|
||||
ngx_devpoll_commands, /* module directives */
|
||||
NGX_EVENT_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_devpoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
|
||||
{
|
||||
size_t n;
|
||||
ngx_devpoll_conf_t *dpcf;
|
||||
|
||||
dpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_devpoll_module);
|
||||
|
||||
if (dp == -1) {
|
||||
dp = open("/dev/poll", O_RDWR);
|
||||
|
||||
if (dp == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
||||
"open(/dev/poll) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (max_changes < dpcf->changes) {
|
||||
if (nchanges) {
|
||||
n = nchanges * sizeof(struct pollfd);
|
||||
if (write(dp, change_list, n) != (ssize_t) n) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"write(/dev/poll) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
nchanges = 0;
|
||||
}
|
||||
|
||||
if (change_list) {
|
||||
ngx_free(change_list);
|
||||
}
|
||||
|
||||
change_list = ngx_alloc(sizeof(struct pollfd) * dpcf->changes,
|
||||
cycle->log);
|
||||
if (change_list == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (change_index) {
|
||||
ngx_free(change_index);
|
||||
}
|
||||
|
||||
change_index = ngx_alloc(sizeof(ngx_event_t *) * dpcf->changes,
|
||||
cycle->log);
|
||||
if (change_index == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
max_changes = dpcf->changes;
|
||||
|
||||
if (nevents < dpcf->events) {
|
||||
if (event_list) {
|
||||
ngx_free(event_list);
|
||||
}
|
||||
|
||||
event_list = ngx_alloc(sizeof(struct pollfd) * dpcf->events,
|
||||
cycle->log);
|
||||
if (event_list == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
nevents = dpcf->events;
|
||||
|
||||
ngx_io = ngx_os_io;
|
||||
|
||||
ngx_event_actions = ngx_devpoll_module_ctx.actions;
|
||||
|
||||
ngx_event_flags = NGX_USE_LEVEL_EVENT|NGX_USE_FD_EVENT;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_devpoll_done(ngx_cycle_t *cycle)
|
||||
{
|
||||
if (close(dp) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"close(/dev/poll) failed");
|
||||
}
|
||||
|
||||
dp = -1;
|
||||
|
||||
ngx_free(change_list);
|
||||
ngx_free(event_list);
|
||||
ngx_free(change_index);
|
||||
|
||||
change_list = NULL;
|
||||
event_list = NULL;
|
||||
change_index = NULL;
|
||||
max_changes = 0;
|
||||
nchanges = 0;
|
||||
nevents = 0;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_devpoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
#if (NGX_DEBUG)
|
||||
ngx_connection_t *c;
|
||||
#endif
|
||||
|
||||
#if (NGX_READ_EVENT != POLLIN)
|
||||
event = (event == NGX_READ_EVENT) ? POLLIN : POLLOUT;
|
||||
#endif
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
c = ev->data;
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"devpoll add event: fd:%d ev:%04Xi", c->fd, event);
|
||||
#endif
|
||||
|
||||
ev->active = 1;
|
||||
|
||||
return ngx_devpoll_set_event(ev, event, 0);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_devpoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
#if (NGX_READ_EVENT != POLLIN)
|
||||
event = (event == NGX_READ_EVENT) ? POLLIN : POLLOUT;
|
||||
#endif
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"devpoll del event: fd:%d ev:%04Xi", c->fd, event);
|
||||
|
||||
if (ngx_devpoll_set_event(ev, POLLREMOVE, flags) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ev->active = 0;
|
||||
|
||||
if (flags & NGX_CLOSE_EVENT) {
|
||||
e = (event == POLLIN) ? c->write : c->read;
|
||||
|
||||
if (e) {
|
||||
e->active = 0;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* restore the pair event if it exists */
|
||||
|
||||
if (event == POLLIN) {
|
||||
e = c->write;
|
||||
event = POLLOUT;
|
||||
|
||||
} else {
|
||||
e = c->read;
|
||||
event = POLLIN;
|
||||
}
|
||||
|
||||
if (e && e->active) {
|
||||
return ngx_devpoll_set_event(e, event, 0);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_devpoll_set_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
size_t n;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"devpoll fd:%d ev:%04Xi fl:%04Xi", c->fd, event, flags);
|
||||
|
||||
if (nchanges >= max_changes) {
|
||||
ngx_log_error(NGX_LOG_WARN, ev->log, 0,
|
||||
"/dev/pool change list is filled up");
|
||||
|
||||
n = nchanges * sizeof(struct pollfd);
|
||||
if (write(dp, change_list, n) != (ssize_t) n) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
|
||||
"write(/dev/poll) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
nchanges = 0;
|
||||
}
|
||||
|
||||
change_list[nchanges].fd = c->fd;
|
||||
change_list[nchanges].events = (short) event;
|
||||
change_list[nchanges].revents = 0;
|
||||
|
||||
change_index[nchanges] = ev;
|
||||
ev->index = nchanges;
|
||||
|
||||
nchanges++;
|
||||
|
||||
if (flags & NGX_CLOSE_EVENT) {
|
||||
n = nchanges * sizeof(struct pollfd);
|
||||
if (write(dp, change_list, n) != (ssize_t) n) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
|
||||
"write(/dev/poll) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
nchanges = 0;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_devpoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags)
|
||||
{
|
||||
int events, revents, rc;
|
||||
size_t n;
|
||||
ngx_fd_t fd;
|
||||
ngx_err_t err;
|
||||
ngx_int_t i;
|
||||
ngx_uint_t level, instance;
|
||||
ngx_event_t *rev, *wev;
|
||||
ngx_queue_t *queue;
|
||||
ngx_connection_t *c;
|
||||
struct pollfd pfd;
|
||||
struct dvpoll dvp;
|
||||
|
||||
/* NGX_TIMER_INFINITE == INFTIM */
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"devpoll timer: %M", timer);
|
||||
|
||||
if (nchanges) {
|
||||
n = nchanges * sizeof(struct pollfd);
|
||||
if (write(dp, change_list, n) != (ssize_t) n) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"write(/dev/poll) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
nchanges = 0;
|
||||
}
|
||||
|
||||
dvp.dp_fds = event_list;
|
||||
dvp.dp_nfds = (int) nevents;
|
||||
dvp.dp_timeout = timer;
|
||||
events = ioctl(dp, DP_POLL, &dvp);
|
||||
|
||||
err = (events == -1) ? ngx_errno : 0;
|
||||
|
||||
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
|
||||
ngx_time_update();
|
||||
}
|
||||
|
||||
if (err) {
|
||||
if (err == NGX_EINTR) {
|
||||
|
||||
if (ngx_event_timer_alarm) {
|
||||
ngx_event_timer_alarm = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
level = NGX_LOG_INFO;
|
||||
|
||||
} else {
|
||||
level = NGX_LOG_ALERT;
|
||||
}
|
||||
|
||||
ngx_log_error(level, cycle->log, err, "ioctl(DP_POLL) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (events == 0) {
|
||||
if (timer != NGX_TIMER_INFINITE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"ioctl(DP_POLL) returned no events without timeout");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; i < events; i++) {
|
||||
|
||||
fd = event_list[i].fd;
|
||||
revents = event_list[i].revents;
|
||||
|
||||
c = ngx_cycle->files[fd];
|
||||
|
||||
if (c == NULL || c->fd == -1) {
|
||||
|
||||
pfd.fd = fd;
|
||||
pfd.events = 0;
|
||||
pfd.revents = 0;
|
||||
|
||||
rc = ioctl(dp, DP_ISPOLLED, &pfd);
|
||||
|
||||
switch (rc) {
|
||||
|
||||
case -1:
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"ioctl(DP_ISPOLLED) failed for socket %d, event %04Xd",
|
||||
fd, revents);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"phantom event %04Xd for closed and removed socket %d",
|
||||
revents, fd);
|
||||
break;
|
||||
|
||||
default:
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"unexpected event %04Xd for closed and removed socket %d, "
|
||||
"ioctl(DP_ISPOLLED) returned rc:%d, fd:%d, event %04Xd",
|
||||
revents, fd, rc, pfd.fd, pfd.revents);
|
||||
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLREMOVE;
|
||||
pfd.revents = 0;
|
||||
|
||||
if (write(dp, &pfd, sizeof(struct pollfd))
|
||||
!= (ssize_t) sizeof(struct pollfd))
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"write(/dev/poll) for %d failed", fd);
|
||||
}
|
||||
|
||||
if (close(fd) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"close(%d) failed", fd);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"devpoll: fd:%d, ev:%04Xd, rev:%04Xd",
|
||||
fd, event_list[i].events, revents);
|
||||
|
||||
if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"ioctl(DP_POLL) error fd:%d ev:%04Xd rev:%04Xd",
|
||||
fd, event_list[i].events, revents);
|
||||
}
|
||||
|
||||
if (revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"strange ioctl(DP_POLL) events "
|
||||
"fd:%d ev:%04Xd rev:%04Xd",
|
||||
fd, event_list[i].events, revents);
|
||||
}
|
||||
|
||||
if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
|
||||
|
||||
/*
|
||||
* if the error events were returned, add POLLIN and POLLOUT
|
||||
* to handle the events at least in one active handler
|
||||
*/
|
||||
|
||||
revents |= POLLIN|POLLOUT;
|
||||
}
|
||||
|
||||
rev = c->read;
|
||||
|
||||
if ((revents & POLLIN) && rev->active) {
|
||||
rev->ready = 1;
|
||||
rev->available = -1;
|
||||
|
||||
if (flags & NGX_POST_EVENTS) {
|
||||
queue = rev->accept ? &ngx_posted_accept_events
|
||||
: &ngx_posted_events;
|
||||
|
||||
ngx_post_event(rev, queue);
|
||||
|
||||
} else {
|
||||
instance = rev->instance;
|
||||
|
||||
rev->handler(rev);
|
||||
|
||||
if (c->fd == -1 || rev->instance != instance) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wev = c->write;
|
||||
|
||||
if ((revents & POLLOUT) && wev->active) {
|
||||
wev->ready = 1;
|
||||
|
||||
if (flags & NGX_POST_EVENTS) {
|
||||
ngx_post_event(wev, &ngx_posted_events);
|
||||
|
||||
} else {
|
||||
wev->handler(wev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_devpoll_create_conf(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_devpoll_conf_t *dpcf;
|
||||
|
||||
dpcf = ngx_palloc(cycle->pool, sizeof(ngx_devpoll_conf_t));
|
||||
if (dpcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dpcf->changes = NGX_CONF_UNSET;
|
||||
dpcf->events = NGX_CONF_UNSET;
|
||||
|
||||
return dpcf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_devpoll_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_devpoll_conf_t *dpcf = conf;
|
||||
|
||||
ngx_conf_init_uint_value(dpcf->changes, 32);
|
||||
ngx_conf_init_uint_value(dpcf->events, 32);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,1051 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
#if (NGX_TEST_BUILD_EPOLL)
|
||||
|
||||
/* epoll declarations */
|
||||
|
||||
#define EPOLLIN 0x001
|
||||
#define EPOLLPRI 0x002
|
||||
#define EPOLLOUT 0x004
|
||||
#define EPOLLERR 0x008
|
||||
#define EPOLLHUP 0x010
|
||||
#define EPOLLRDNORM 0x040
|
||||
#define EPOLLRDBAND 0x080
|
||||
#define EPOLLWRNORM 0x100
|
||||
#define EPOLLWRBAND 0x200
|
||||
#define EPOLLMSG 0x400
|
||||
|
||||
#define EPOLLRDHUP 0x2000
|
||||
|
||||
#define EPOLLEXCLUSIVE 0x10000000
|
||||
#define EPOLLONESHOT 0x40000000
|
||||
#define EPOLLET 0x80000000
|
||||
|
||||
#define EPOLL_CTL_ADD 1
|
||||
#define EPOLL_CTL_DEL 2
|
||||
#define EPOLL_CTL_MOD 3
|
||||
|
||||
typedef union epoll_data {
|
||||
void *ptr;
|
||||
int fd;
|
||||
uint32_t u32;
|
||||
uint64_t u64;
|
||||
} epoll_data_t;
|
||||
|
||||
struct epoll_event {
|
||||
uint32_t events;
|
||||
epoll_data_t data;
|
||||
};
|
||||
|
||||
|
||||
int epoll_create(int size);
|
||||
|
||||
int epoll_create(int size)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
|
||||
|
||||
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int epoll_wait(int epfd, struct epoll_event *events, int nevents, int timeout);
|
||||
|
||||
int epoll_wait(int epfd, struct epoll_event *events, int nevents, int timeout)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_EVENTFD)
|
||||
#define SYS_eventfd 323
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_FILE_AIO)
|
||||
|
||||
#define SYS_io_setup 245
|
||||
#define SYS_io_destroy 246
|
||||
#define SYS_io_getevents 247
|
||||
|
||||
typedef u_int aio_context_t;
|
||||
|
||||
struct io_event {
|
||||
uint64_t data; /* the data field from the iocb */
|
||||
uint64_t obj; /* what iocb this event came from */
|
||||
int64_t res; /* result code for this event */
|
||||
int64_t res2; /* secondary result */
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
#endif /* NGX_TEST_BUILD_EPOLL */
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t events;
|
||||
ngx_uint_t aio_requests;
|
||||
} ngx_epoll_conf_t;
|
||||
|
||||
|
||||
static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer);
|
||||
#if (NGX_HAVE_EVENTFD)
|
||||
static ngx_int_t ngx_epoll_notify_init(ngx_log_t *log);
|
||||
static void ngx_epoll_notify_handler(ngx_event_t *ev);
|
||||
#endif
|
||||
#if (NGX_HAVE_EPOLLRDHUP)
|
||||
static void ngx_epoll_test_rdhup(ngx_cycle_t *cycle);
|
||||
#endif
|
||||
static void ngx_epoll_done(ngx_cycle_t *cycle);
|
||||
static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_epoll_add_connection(ngx_connection_t *c);
|
||||
static ngx_int_t ngx_epoll_del_connection(ngx_connection_t *c,
|
||||
ngx_uint_t flags);
|
||||
#if (NGX_HAVE_EVENTFD)
|
||||
static ngx_int_t ngx_epoll_notify(ngx_event_handler_pt handler);
|
||||
#endif
|
||||
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags);
|
||||
|
||||
#if (NGX_HAVE_FILE_AIO)
|
||||
static void ngx_epoll_eventfd_handler(ngx_event_t *ev);
|
||||
#endif
|
||||
|
||||
static void *ngx_epoll_create_conf(ngx_cycle_t *cycle);
|
||||
static char *ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
static int ep = -1;
|
||||
static struct epoll_event *event_list;
|
||||
static ngx_uint_t nevents;
|
||||
|
||||
#if (NGX_HAVE_EVENTFD)
|
||||
static int notify_fd = -1;
|
||||
static ngx_event_t notify_event;
|
||||
static ngx_connection_t notify_conn;
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_FILE_AIO)
|
||||
|
||||
int ngx_eventfd = -1;
|
||||
aio_context_t ngx_aio_ctx = 0;
|
||||
|
||||
static ngx_event_t ngx_eventfd_event;
|
||||
static ngx_connection_t ngx_eventfd_conn;
|
||||
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_EPOLLRDHUP)
|
||||
ngx_uint_t ngx_use_epoll_rdhup;
|
||||
#endif
|
||||
|
||||
static ngx_str_t epoll_name = ngx_string("epoll");
|
||||
|
||||
static ngx_command_t ngx_epoll_commands[] = {
|
||||
|
||||
{ ngx_string("epoll_events"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
0,
|
||||
offsetof(ngx_epoll_conf_t, events),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("worker_aio_requests"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
0,
|
||||
offsetof(ngx_epoll_conf_t, aio_requests),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_event_module_t ngx_epoll_module_ctx = {
|
||||
&epoll_name,
|
||||
ngx_epoll_create_conf, /* create configuration */
|
||||
ngx_epoll_init_conf, /* init configuration */
|
||||
|
||||
{
|
||||
ngx_epoll_add_event, /* add an event */
|
||||
ngx_epoll_del_event, /* delete an event */
|
||||
ngx_epoll_add_event, /* enable an event */
|
||||
ngx_epoll_del_event, /* disable an event */
|
||||
ngx_epoll_add_connection, /* add an connection */
|
||||
ngx_epoll_del_connection, /* delete an connection */
|
||||
#if (NGX_HAVE_EVENTFD)
|
||||
ngx_epoll_notify, /* trigger a notify */
|
||||
#else
|
||||
NULL, /* trigger a notify */
|
||||
#endif
|
||||
ngx_epoll_process_events, /* process the events */
|
||||
ngx_epoll_init, /* init the events */
|
||||
ngx_epoll_done, /* done the events */
|
||||
}
|
||||
};
|
||||
|
||||
ngx_module_t ngx_epoll_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_epoll_module_ctx, /* module context */
|
||||
ngx_epoll_commands, /* module directives */
|
||||
NGX_EVENT_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
#if (NGX_HAVE_FILE_AIO)
|
||||
|
||||
/*
|
||||
* We call io_setup(), io_destroy() io_submit(), and io_getevents() directly
|
||||
* as syscalls instead of libaio usage, because the library header file
|
||||
* supports eventfd() since 0.3.107 version only.
|
||||
*/
|
||||
|
||||
static int
|
||||
io_setup(u_int nr_reqs, aio_context_t *ctx)
|
||||
{
|
||||
return syscall(SYS_io_setup, nr_reqs, ctx);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
io_destroy(aio_context_t ctx)
|
||||
{
|
||||
return syscall(SYS_io_destroy, ctx);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events,
|
||||
struct timespec *tmo)
|
||||
{
|
||||
return syscall(SYS_io_getevents, ctx, min_nr, nr, events, tmo);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_epoll_aio_init(ngx_cycle_t *cycle, ngx_epoll_conf_t *epcf)
|
||||
{
|
||||
int n;
|
||||
struct epoll_event ee;
|
||||
|
||||
#if (NGX_HAVE_SYS_EVENTFD_H)
|
||||
ngx_eventfd = eventfd(0, 0);
|
||||
#else
|
||||
ngx_eventfd = syscall(SYS_eventfd, 0);
|
||||
#endif
|
||||
|
||||
if (ngx_eventfd == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
||||
"eventfd() failed");
|
||||
ngx_file_aio = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"eventfd: %d", ngx_eventfd);
|
||||
|
||||
n = 1;
|
||||
|
||||
if (ioctl(ngx_eventfd, FIONBIO, &n) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
||||
"ioctl(eventfd, FIONBIO) failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (io_setup(epcf->aio_requests, &ngx_aio_ctx) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
||||
"io_setup() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
ngx_eventfd_event.data = &ngx_eventfd_conn;
|
||||
ngx_eventfd_event.handler = ngx_epoll_eventfd_handler;
|
||||
ngx_eventfd_event.log = cycle->log;
|
||||
ngx_eventfd_event.active = 1;
|
||||
ngx_eventfd_conn.fd = ngx_eventfd;
|
||||
ngx_eventfd_conn.read = &ngx_eventfd_event;
|
||||
ngx_eventfd_conn.log = cycle->log;
|
||||
|
||||
ee.events = EPOLLIN|EPOLLET;
|
||||
ee.data.ptr = &ngx_eventfd_conn;
|
||||
|
||||
if (epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
||||
"epoll_ctl(EPOLL_CTL_ADD, eventfd) failed");
|
||||
|
||||
if (io_destroy(ngx_aio_ctx) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"io_destroy() failed");
|
||||
}
|
||||
|
||||
failed:
|
||||
|
||||
if (close(ngx_eventfd) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"eventfd close() failed");
|
||||
}
|
||||
|
||||
ngx_eventfd = -1;
|
||||
ngx_aio_ctx = 0;
|
||||
ngx_file_aio = 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
|
||||
{
|
||||
ngx_epoll_conf_t *epcf;
|
||||
|
||||
epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
|
||||
|
||||
if (ep == -1) {
|
||||
ep = epoll_create(cycle->connection_n / 2);
|
||||
|
||||
if (ep == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
||||
"epoll_create() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_EVENTFD)
|
||||
if (ngx_epoll_notify_init(cycle->log) != NGX_OK) {
|
||||
ngx_epoll_module_ctx.actions.notify = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_FILE_AIO)
|
||||
ngx_epoll_aio_init(cycle, epcf);
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_EPOLLRDHUP)
|
||||
ngx_epoll_test_rdhup(cycle);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (nevents < epcf->events) {
|
||||
if (event_list) {
|
||||
ngx_free(event_list);
|
||||
}
|
||||
|
||||
event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
|
||||
cycle->log);
|
||||
if (event_list == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
nevents = epcf->events;
|
||||
|
||||
ngx_io = ngx_os_io;
|
||||
|
||||
ngx_event_actions = ngx_epoll_module_ctx.actions;
|
||||
|
||||
#if (NGX_HAVE_CLEAR_EVENT)
|
||||
ngx_event_flags = NGX_USE_CLEAR_EVENT
|
||||
#else
|
||||
ngx_event_flags = NGX_USE_LEVEL_EVENT
|
||||
#endif
|
||||
|NGX_USE_GREEDY_EVENT
|
||||
|NGX_USE_EPOLL_EVENT;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#if (NGX_HAVE_EVENTFD)
|
||||
|
||||
static ngx_int_t
|
||||
ngx_epoll_notify_init(ngx_log_t *log)
|
||||
{
|
||||
struct epoll_event ee;
|
||||
|
||||
#if (NGX_HAVE_SYS_EVENTFD_H)
|
||||
notify_fd = eventfd(0, 0);
|
||||
#else
|
||||
notify_fd = syscall(SYS_eventfd, 0);
|
||||
#endif
|
||||
|
||||
if (notify_fd == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "eventfd() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
|
||||
"notify eventfd: %d", notify_fd);
|
||||
|
||||
notify_event.handler = ngx_epoll_notify_handler;
|
||||
notify_event.log = log;
|
||||
notify_event.active = 1;
|
||||
|
||||
notify_conn.fd = notify_fd;
|
||||
notify_conn.read = ¬ify_event;
|
||||
notify_conn.log = log;
|
||||
|
||||
ee.events = EPOLLIN|EPOLLET;
|
||||
ee.data.ptr = ¬ify_conn;
|
||||
|
||||
if (epoll_ctl(ep, EPOLL_CTL_ADD, notify_fd, &ee) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
|
||||
"epoll_ctl(EPOLL_CTL_ADD, eventfd) failed");
|
||||
|
||||
if (close(notify_fd) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
|
||||
"eventfd close() failed");
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_epoll_notify_handler(ngx_event_t *ev)
|
||||
{
|
||||
ssize_t n;
|
||||
uint64_t count;
|
||||
ngx_err_t err;
|
||||
ngx_event_handler_pt handler;
|
||||
|
||||
if (++ev->index == NGX_MAX_UINT32_VALUE) {
|
||||
ev->index = 0;
|
||||
|
||||
n = read(notify_fd, &count, sizeof(uint64_t));
|
||||
|
||||
err = ngx_errno;
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"read() eventfd %d: %z count:%uL", notify_fd, n, count);
|
||||
|
||||
if ((size_t) n != sizeof(uint64_t)) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, err,
|
||||
"read() eventfd %d failed", notify_fd);
|
||||
}
|
||||
}
|
||||
|
||||
handler = ev->data;
|
||||
handler(ev);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if (NGX_HAVE_EPOLLRDHUP)
|
||||
|
||||
static void
|
||||
ngx_epoll_test_rdhup(ngx_cycle_t *cycle)
|
||||
{
|
||||
int s[2], events;
|
||||
struct epoll_event ee;
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"socketpair() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
ee.events = EPOLLET|EPOLLIN|EPOLLRDHUP;
|
||||
|
||||
if (epoll_ctl(ep, EPOLL_CTL_ADD, s[0], &ee) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"epoll_ctl() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (close(s[1]) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"close() failed");
|
||||
s[1] = -1;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
s[1] = -1;
|
||||
|
||||
events = epoll_wait(ep, &ee, 1, 5000);
|
||||
|
||||
if (events == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"epoll_wait() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (events) {
|
||||
ngx_use_epoll_rdhup = ee.events & EPOLLRDHUP;
|
||||
|
||||
} else {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, NGX_ETIMEDOUT,
|
||||
"epoll_wait() timed out");
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"testing the EPOLLRDHUP flag: %s",
|
||||
ngx_use_epoll_rdhup ? "success" : "fail");
|
||||
|
||||
failed:
|
||||
|
||||
if (s[1] != -1 && close(s[1]) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"close() failed");
|
||||
}
|
||||
|
||||
if (close(s[0]) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"close() failed");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static void
|
||||
ngx_epoll_done(ngx_cycle_t *cycle)
|
||||
{
|
||||
if (close(ep) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"epoll close() failed");
|
||||
}
|
||||
|
||||
ep = -1;
|
||||
|
||||
#if (NGX_HAVE_EVENTFD)
|
||||
|
||||
if (close(notify_fd) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"eventfd close() failed");
|
||||
}
|
||||
|
||||
notify_fd = -1;
|
||||
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_FILE_AIO)
|
||||
|
||||
if (ngx_eventfd != -1) {
|
||||
|
||||
if (io_destroy(ngx_aio_ctx) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"io_destroy() failed");
|
||||
}
|
||||
|
||||
if (close(ngx_eventfd) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"eventfd close() failed");
|
||||
}
|
||||
|
||||
ngx_eventfd = -1;
|
||||
}
|
||||
|
||||
ngx_aio_ctx = 0;
|
||||
|
||||
#endif
|
||||
|
||||
ngx_free(event_list);
|
||||
|
||||
event_list = NULL;
|
||||
nevents = 0;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
int op;
|
||||
uint32_t events, prev;
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
struct epoll_event ee;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
events = (uint32_t) event;
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
e = c->write;
|
||||
prev = EPOLLOUT;
|
||||
#if (NGX_READ_EVENT != EPOLLIN|EPOLLRDHUP)
|
||||
events = EPOLLIN|EPOLLRDHUP;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
e = c->read;
|
||||
prev = EPOLLIN|EPOLLRDHUP;
|
||||
#if (NGX_WRITE_EVENT != EPOLLOUT)
|
||||
events = EPOLLOUT;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (e->active) {
|
||||
op = EPOLL_CTL_MOD;
|
||||
events |= prev;
|
||||
|
||||
} else {
|
||||
op = EPOLL_CTL_ADD;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_EPOLLEXCLUSIVE && NGX_HAVE_EPOLLRDHUP)
|
||||
if (flags & NGX_EXCLUSIVE_EVENT) {
|
||||
events &= ~EPOLLRDHUP;
|
||||
}
|
||||
#endif
|
||||
|
||||
ee.events = events | (uint32_t) flags;
|
||||
ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"epoll add event: fd:%d op:%d ev:%08XD",
|
||||
c->fd, op, ee.events);
|
||||
|
||||
if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
|
||||
"epoll_ctl(%d, %d) failed", op, c->fd);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ev->active = 1;
|
||||
#if 0
|
||||
ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
int op;
|
||||
uint32_t prev;
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
struct epoll_event ee;
|
||||
|
||||
/*
|
||||
* when the file descriptor is closed, the epoll automatically deletes
|
||||
* it from its queue, so we do not need to delete explicitly the event
|
||||
* before the closing the file descriptor
|
||||
*/
|
||||
|
||||
if (flags & NGX_CLOSE_EVENT) {
|
||||
ev->active = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
c = ev->data;
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
e = c->write;
|
||||
prev = EPOLLOUT;
|
||||
|
||||
} else {
|
||||
e = c->read;
|
||||
prev = EPOLLIN|EPOLLRDHUP;
|
||||
}
|
||||
|
||||
if (e->active) {
|
||||
op = EPOLL_CTL_MOD;
|
||||
ee.events = prev | (uint32_t) flags;
|
||||
ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
|
||||
|
||||
} else {
|
||||
op = EPOLL_CTL_DEL;
|
||||
ee.events = 0;
|
||||
ee.data.ptr = NULL;
|
||||
}
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"epoll del event: fd:%d op:%d ev:%08XD",
|
||||
c->fd, op, ee.events);
|
||||
|
||||
if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
|
||||
"epoll_ctl(%d, %d) failed", op, c->fd);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ev->active = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_epoll_add_connection(ngx_connection_t *c)
|
||||
{
|
||||
struct epoll_event ee;
|
||||
|
||||
ee.events = EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP;
|
||||
ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"epoll add connection: fd:%d ev:%08XD", c->fd, ee.events);
|
||||
|
||||
if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
|
||||
"epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->read->active = 1;
|
||||
c->write->active = 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_epoll_del_connection(ngx_connection_t *c, ngx_uint_t flags)
|
||||
{
|
||||
int op;
|
||||
struct epoll_event ee;
|
||||
|
||||
/*
|
||||
* when the file descriptor is closed the epoll automatically deletes
|
||||
* it from its queue so we do not need to delete explicitly the event
|
||||
* before the closing the file descriptor
|
||||
*/
|
||||
|
||||
if (flags & NGX_CLOSE_EVENT) {
|
||||
c->read->active = 0;
|
||||
c->write->active = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"epoll del connection: fd:%d", c->fd);
|
||||
|
||||
op = EPOLL_CTL_DEL;
|
||||
ee.events = 0;
|
||||
ee.data.ptr = NULL;
|
||||
|
||||
if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
|
||||
"epoll_ctl(%d, %d) failed", op, c->fd);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->read->active = 0;
|
||||
c->write->active = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#if (NGX_HAVE_EVENTFD)
|
||||
|
||||
static ngx_int_t
|
||||
ngx_epoll_notify(ngx_event_handler_pt handler)
|
||||
{
|
||||
static uint64_t inc = 1;
|
||||
|
||||
notify_event.data = handler;
|
||||
|
||||
if ((size_t) write(notify_fd, &inc, sizeof(uint64_t)) != sizeof(uint64_t)) {
|
||||
ngx_log_error(NGX_LOG_ALERT, notify_event.log, ngx_errno,
|
||||
"write() to eventfd %d failed", notify_fd);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
|
||||
{
|
||||
int events;
|
||||
uint32_t revents;
|
||||
ngx_int_t instance, i;
|
||||
ngx_uint_t level;
|
||||
ngx_err_t err;
|
||||
ngx_event_t *rev, *wev;
|
||||
ngx_queue_t *queue;
|
||||
ngx_connection_t *c;
|
||||
|
||||
/* NGX_TIMER_INFINITE == INFTIM */
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"epoll timer: %M", timer);
|
||||
|
||||
events = epoll_wait(ep, event_list, (int) nevents, timer);
|
||||
|
||||
err = (events == -1) ? ngx_errno : 0;
|
||||
|
||||
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
|
||||
ngx_time_update();
|
||||
}
|
||||
|
||||
if (err) {
|
||||
if (err == NGX_EINTR) {
|
||||
|
||||
if (ngx_event_timer_alarm) {
|
||||
ngx_event_timer_alarm = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
level = NGX_LOG_INFO;
|
||||
|
||||
} else {
|
||||
level = NGX_LOG_ALERT;
|
||||
}
|
||||
|
||||
ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (events == 0) {
|
||||
if (timer != NGX_TIMER_INFINITE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"epoll_wait() returned no events without timeout");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; i < events; i++) {
|
||||
c = event_list[i].data.ptr;
|
||||
|
||||
instance = (uintptr_t) c & 1;
|
||||
c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
|
||||
|
||||
rev = c->read;
|
||||
|
||||
if (c->fd == -1 || rev->instance != instance) {
|
||||
|
||||
/*
|
||||
* the stale event from a file descriptor
|
||||
* that was just closed in this iteration
|
||||
*/
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"epoll: stale event %p", c);
|
||||
continue;
|
||||
}
|
||||
|
||||
revents = event_list[i].events;
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"epoll: fd:%d ev:%04XD d:%p",
|
||||
c->fd, revents, event_list[i].data.ptr);
|
||||
|
||||
if (revents & (EPOLLERR|EPOLLHUP)) {
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"epoll_wait() error on fd:%d ev:%04XD",
|
||||
c->fd, revents);
|
||||
|
||||
/*
|
||||
* if the error events were returned, add EPOLLIN and EPOLLOUT
|
||||
* to handle the events at least in one active handler
|
||||
*/
|
||||
|
||||
revents |= EPOLLIN|EPOLLOUT;
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"strange epoll_wait() events fd:%d ev:%04XD",
|
||||
c->fd, revents);
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((revents & EPOLLIN) && rev->active) {
|
||||
|
||||
#if (NGX_HAVE_EPOLLRDHUP)
|
||||
if (revents & EPOLLRDHUP) {
|
||||
rev->pending_eof = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
rev->ready = 1;
|
||||
rev->available = -1;
|
||||
|
||||
if (flags & NGX_POST_EVENTS) {
|
||||
queue = rev->accept ? &ngx_posted_accept_events
|
||||
: &ngx_posted_events;
|
||||
|
||||
ngx_post_event(rev, queue);
|
||||
|
||||
} else {
|
||||
rev->handler(rev);
|
||||
}
|
||||
}
|
||||
|
||||
wev = c->write;
|
||||
|
||||
if ((revents & EPOLLOUT) && wev->active) {
|
||||
|
||||
if (c->fd == -1 || wev->instance != instance) {
|
||||
|
||||
/*
|
||||
* the stale event from a file descriptor
|
||||
* that was just closed in this iteration
|
||||
*/
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"epoll: stale event %p", c);
|
||||
continue;
|
||||
}
|
||||
|
||||
wev->ready = 1;
|
||||
#if (NGX_THREADS)
|
||||
wev->complete = 1;
|
||||
#endif
|
||||
|
||||
if (flags & NGX_POST_EVENTS) {
|
||||
ngx_post_event(wev, &ngx_posted_events);
|
||||
|
||||
} else {
|
||||
wev->handler(wev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#if (NGX_HAVE_FILE_AIO)
|
||||
|
||||
static void
|
||||
ngx_epoll_eventfd_handler(ngx_event_t *ev)
|
||||
{
|
||||
int n, events;
|
||||
long i;
|
||||
uint64_t ready;
|
||||
ngx_err_t err;
|
||||
ngx_event_t *e;
|
||||
ngx_event_aio_t *aio;
|
||||
struct io_event event[64];
|
||||
struct timespec ts;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd handler");
|
||||
|
||||
n = read(ngx_eventfd, &ready, 8);
|
||||
|
||||
err = ngx_errno;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd: %d", n);
|
||||
|
||||
if (n != 8) {
|
||||
if (n == -1) {
|
||||
if (err == NGX_EAGAIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, err, "read(eventfd) failed");
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"read(eventfd) returned only %d bytes", n);
|
||||
return;
|
||||
}
|
||||
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 0;
|
||||
|
||||
while (ready) {
|
||||
|
||||
events = io_getevents(ngx_aio_ctx, 1, 64, event, &ts);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"io_getevents: %d", events);
|
||||
|
||||
if (events > 0) {
|
||||
ready -= events;
|
||||
|
||||
for (i = 0; i < events; i++) {
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"io_event: %XL %XL %L %L",
|
||||
event[i].data, event[i].obj,
|
||||
event[i].res, event[i].res2);
|
||||
|
||||
e = (ngx_event_t *) (uintptr_t) event[i].data;
|
||||
|
||||
e->complete = 1;
|
||||
e->active = 0;
|
||||
e->ready = 1;
|
||||
|
||||
aio = e->data;
|
||||
aio->res = event[i].res;
|
||||
|
||||
ngx_post_event(e, &ngx_posted_events);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (events == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* events == -1 */
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
|
||||
"io_getevents() failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static void *
|
||||
ngx_epoll_create_conf(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_epoll_conf_t *epcf;
|
||||
|
||||
epcf = ngx_palloc(cycle->pool, sizeof(ngx_epoll_conf_t));
|
||||
if (epcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
epcf->events = NGX_CONF_UNSET;
|
||||
epcf->aio_requests = NGX_CONF_UNSET;
|
||||
|
||||
return epcf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_epoll_conf_t *epcf = conf;
|
||||
|
||||
ngx_conf_init_uint_value(epcf->events, 512);
|
||||
ngx_conf_init_uint_value(epcf->aio_requests, 32);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
#if (NGX_TEST_BUILD_EVENTPORT)
|
||||
|
||||
#define ushort_t u_short
|
||||
#define uint_t u_int
|
||||
|
||||
#ifndef CLOCK_REALTIME
|
||||
#define CLOCK_REALTIME 0
|
||||
typedef int clockid_t;
|
||||
typedef void * timer_t;
|
||||
#elif (NGX_DARWIN)
|
||||
typedef void * timer_t;
|
||||
#endif
|
||||
|
||||
/* Solaris declarations */
|
||||
|
||||
#define PORT_SOURCE_AIO 1
|
||||
#define PORT_SOURCE_TIMER 2
|
||||
#define PORT_SOURCE_USER 3
|
||||
#define PORT_SOURCE_FD 4
|
||||
#define PORT_SOURCE_ALERT 5
|
||||
#define PORT_SOURCE_MQ 6
|
||||
|
||||
#ifndef ETIME
|
||||
#define ETIME 64
|
||||
#endif
|
||||
|
||||
#define SIGEV_PORT 4
|
||||
|
||||
typedef struct {
|
||||
int portev_events; /* event data is source specific */
|
||||
ushort_t portev_source; /* event source */
|
||||
ushort_t portev_pad; /* port internal use */
|
||||
uintptr_t portev_object; /* source specific object */
|
||||
void *portev_user; /* user cookie */
|
||||
} port_event_t;
|
||||
|
||||
typedef struct port_notify {
|
||||
int portnfy_port; /* bind request(s) to port */
|
||||
void *portnfy_user; /* user defined */
|
||||
} port_notify_t;
|
||||
|
||||
#if (__FreeBSD__ && __FreeBSD_version < 700005) || (NGX_DARWIN)
|
||||
|
||||
typedef struct itimerspec { /* definition per POSIX.4 */
|
||||
struct timespec it_interval;/* timer period */
|
||||
struct timespec it_value; /* timer expiration */
|
||||
} itimerspec_t;
|
||||
|
||||
#endif
|
||||
|
||||
int port_create(void);
|
||||
|
||||
int port_create(void)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int port_associate(int port, int source, uintptr_t object, int events,
|
||||
void *user);
|
||||
|
||||
int port_associate(int port, int source, uintptr_t object, int events,
|
||||
void *user)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int port_dissociate(int port, int source, uintptr_t object);
|
||||
|
||||
int port_dissociate(int port, int source, uintptr_t object)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int port_getn(int port, port_event_t list[], uint_t max, uint_t *nget,
|
||||
struct timespec *timeout);
|
||||
|
||||
int port_getn(int port, port_event_t list[], uint_t max, uint_t *nget,
|
||||
struct timespec *timeout)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int port_send(int port, int events, void *user);
|
||||
|
||||
int port_send(int port, int events, void *user)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid);
|
||||
|
||||
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int timer_settime(timer_t timerid, int flags, const struct itimerspec *value,
|
||||
struct itimerspec *ovalue);
|
||||
|
||||
int timer_settime(timer_t timerid, int flags, const struct itimerspec *value,
|
||||
struct itimerspec *ovalue)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int timer_delete(timer_t timerid);
|
||||
|
||||
int timer_delete(timer_t timerid)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t events;
|
||||
} ngx_eventport_conf_t;
|
||||
|
||||
|
||||
static ngx_int_t ngx_eventport_init(ngx_cycle_t *cycle, ngx_msec_t timer);
|
||||
static void ngx_eventport_done(ngx_cycle_t *cycle);
|
||||
static ngx_int_t ngx_eventport_add_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_eventport_del_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_eventport_notify(ngx_event_handler_pt handler);
|
||||
static ngx_int_t ngx_eventport_process_events(ngx_cycle_t *cycle,
|
||||
ngx_msec_t timer, ngx_uint_t flags);
|
||||
|
||||
static void *ngx_eventport_create_conf(ngx_cycle_t *cycle);
|
||||
static char *ngx_eventport_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
static int ep = -1;
|
||||
static port_event_t *event_list;
|
||||
static ngx_uint_t nevents;
|
||||
static timer_t event_timer = (timer_t) -1;
|
||||
static ngx_event_t notify_event;
|
||||
|
||||
static ngx_str_t eventport_name = ngx_string("eventport");
|
||||
|
||||
|
||||
static ngx_command_t ngx_eventport_commands[] = {
|
||||
|
||||
{ ngx_string("eventport_events"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
0,
|
||||
offsetof(ngx_eventport_conf_t, events),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_event_module_t ngx_eventport_module_ctx = {
|
||||
&eventport_name,
|
||||
ngx_eventport_create_conf, /* create configuration */
|
||||
ngx_eventport_init_conf, /* init configuration */
|
||||
|
||||
{
|
||||
ngx_eventport_add_event, /* add an event */
|
||||
ngx_eventport_del_event, /* delete an event */
|
||||
ngx_eventport_add_event, /* enable an event */
|
||||
ngx_eventport_del_event, /* disable an event */
|
||||
NULL, /* add an connection */
|
||||
NULL, /* delete an connection */
|
||||
ngx_eventport_notify, /* trigger a notify */
|
||||
ngx_eventport_process_events, /* process the events */
|
||||
ngx_eventport_init, /* init the events */
|
||||
ngx_eventport_done, /* done the events */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ngx_module_t ngx_eventport_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_eventport_module_ctx, /* module context */
|
||||
ngx_eventport_commands, /* module directives */
|
||||
NGX_EVENT_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_eventport_init(ngx_cycle_t *cycle, ngx_msec_t timer)
|
||||
{
|
||||
port_notify_t pn;
|
||||
struct itimerspec its;
|
||||
struct sigevent sev;
|
||||
ngx_eventport_conf_t *epcf;
|
||||
|
||||
epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_eventport_module);
|
||||
|
||||
if (ep == -1) {
|
||||
ep = port_create();
|
||||
|
||||
if (ep == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
||||
"port_create() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
notify_event.active = 1;
|
||||
notify_event.log = cycle->log;
|
||||
}
|
||||
|
||||
if (nevents < epcf->events) {
|
||||
if (event_list) {
|
||||
ngx_free(event_list);
|
||||
}
|
||||
|
||||
event_list = ngx_alloc(sizeof(port_event_t) * epcf->events,
|
||||
cycle->log);
|
||||
if (event_list == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_event_flags = NGX_USE_EVENTPORT_EVENT;
|
||||
|
||||
if (timer) {
|
||||
ngx_memzero(&pn, sizeof(port_notify_t));
|
||||
pn.portnfy_port = ep;
|
||||
|
||||
ngx_memzero(&sev, sizeof(struct sigevent));
|
||||
sev.sigev_notify = SIGEV_PORT;
|
||||
sev.sigev_value.sival_ptr = &pn;
|
||||
|
||||
if (timer_create(CLOCK_REALTIME, &sev, &event_timer) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
||||
"timer_create() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
its.it_interval.tv_sec = timer / 1000;
|
||||
its.it_interval.tv_nsec = (timer % 1000) * 1000000;
|
||||
its.it_value.tv_sec = timer / 1000;
|
||||
its.it_value.tv_nsec = (timer % 1000) * 1000000;
|
||||
|
||||
if (timer_settime(event_timer, 0, &its, NULL) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
||||
"timer_settime() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_event_flags |= NGX_USE_TIMER_EVENT;
|
||||
}
|
||||
|
||||
nevents = epcf->events;
|
||||
|
||||
ngx_io = ngx_os_io;
|
||||
|
||||
ngx_event_actions = ngx_eventport_module_ctx.actions;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_eventport_done(ngx_cycle_t *cycle)
|
||||
{
|
||||
if (event_timer != (timer_t) -1) {
|
||||
if (timer_delete(event_timer) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"timer_delete() failed");
|
||||
}
|
||||
|
||||
event_timer = (timer_t) -1;
|
||||
}
|
||||
|
||||
if (close(ep) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"close() event port failed");
|
||||
}
|
||||
|
||||
ep = -1;
|
||||
|
||||
ngx_free(event_list);
|
||||
|
||||
event_list = NULL;
|
||||
nevents = 0;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_eventport_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_int_t events, prev;
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
events = event;
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
e = c->write;
|
||||
prev = POLLOUT;
|
||||
#if (NGX_READ_EVENT != POLLIN)
|
||||
events = POLLIN;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
e = c->read;
|
||||
prev = POLLIN;
|
||||
#if (NGX_WRITE_EVENT != POLLOUT)
|
||||
events = POLLOUT;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (e->oneshot) {
|
||||
events |= prev;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"eventport add event: fd:%d ev:%04Xi", c->fd, events);
|
||||
|
||||
if (port_associate(ep, PORT_SOURCE_FD, c->fd, events,
|
||||
(void *) ((uintptr_t) ev | ev->instance))
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
|
||||
"port_associate() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ev->active = 1;
|
||||
ev->oneshot = 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_eventport_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
/*
|
||||
* when the file descriptor is closed, the event port automatically
|
||||
* dissociates it from the port, so we do not need to dissociate explicitly
|
||||
* the event before the closing the file descriptor
|
||||
*/
|
||||
|
||||
if (flags & NGX_CLOSE_EVENT) {
|
||||
ev->active = 0;
|
||||
ev->oneshot = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
c = ev->data;
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
e = c->write;
|
||||
event = POLLOUT;
|
||||
|
||||
} else {
|
||||
e = c->read;
|
||||
event = POLLIN;
|
||||
}
|
||||
|
||||
if (e->oneshot) {
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"eventport change event: fd:%d ev:%04Xi", c->fd, event);
|
||||
|
||||
if (port_associate(ep, PORT_SOURCE_FD, c->fd, event,
|
||||
(void *) ((uintptr_t) ev | ev->instance))
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
|
||||
"port_associate() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
} else if (ev->active) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"eventport del event: fd:%d", c->fd);
|
||||
|
||||
if (port_dissociate(ep, PORT_SOURCE_FD, c->fd) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
|
||||
"port_dissociate() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ev->active = 0;
|
||||
ev->oneshot = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_eventport_notify(ngx_event_handler_pt handler)
|
||||
{
|
||||
notify_event.handler = handler;
|
||||
|
||||
if (port_send(ep, 0, ¬ify_event) != 0) {
|
||||
ngx_log_error(NGX_LOG_ALERT, notify_event.log, ngx_errno,
|
||||
"port_send() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_eventport_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags)
|
||||
{
|
||||
int n, revents;
|
||||
u_int events;
|
||||
ngx_err_t err;
|
||||
ngx_int_t instance;
|
||||
ngx_uint_t i, level;
|
||||
ngx_event_t *ev, *rev, *wev;
|
||||
ngx_queue_t *queue;
|
||||
ngx_connection_t *c;
|
||||
struct timespec ts, *tp;
|
||||
|
||||
if (timer == NGX_TIMER_INFINITE) {
|
||||
tp = NULL;
|
||||
|
||||
} else {
|
||||
ts.tv_sec = timer / 1000;
|
||||
ts.tv_nsec = (timer % 1000) * 1000000;
|
||||
tp = &ts;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"eventport timer: %M", timer);
|
||||
|
||||
events = 1;
|
||||
|
||||
n = port_getn(ep, event_list, (u_int) nevents, &events, tp);
|
||||
|
||||
err = ngx_errno;
|
||||
|
||||
if (flags & NGX_UPDATE_TIME) {
|
||||
ngx_time_update();
|
||||
}
|
||||
|
||||
if (n == -1) {
|
||||
if (err == ETIME) {
|
||||
if (timer != NGX_TIMER_INFINITE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"port_getn() returned no events without timeout");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
level = (err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT;
|
||||
ngx_log_error(level, cycle->log, err, "port_getn() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (events == 0) {
|
||||
if (timer != NGX_TIMER_INFINITE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"port_getn() returned no events without timeout");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; i < events; i++) {
|
||||
|
||||
if (event_list[i].portev_source == PORT_SOURCE_TIMER) {
|
||||
ngx_time_update();
|
||||
continue;
|
||||
}
|
||||
|
||||
ev = event_list[i].portev_user;
|
||||
|
||||
switch (event_list[i].portev_source) {
|
||||
|
||||
case PORT_SOURCE_FD:
|
||||
|
||||
instance = (uintptr_t) ev & 1;
|
||||
ev = (ngx_event_t *) ((uintptr_t) ev & (uintptr_t) ~1);
|
||||
|
||||
if (ev->closed || ev->instance != instance) {
|
||||
|
||||
/*
|
||||
* the stale event from a file descriptor
|
||||
* that was just closed in this iteration
|
||||
*/
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"eventport: stale event %p", ev);
|
||||
continue;
|
||||
}
|
||||
|
||||
revents = event_list[i].portev_events;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"eventport: fd:%d, ev:%04Xd",
|
||||
(int) event_list[i].portev_object, revents);
|
||||
|
||||
if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"port_getn() error fd:%d ev:%04Xd",
|
||||
(int) event_list[i].portev_object, revents);
|
||||
}
|
||||
|
||||
if (revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"strange port_getn() events fd:%d ev:%04Xd",
|
||||
(int) event_list[i].portev_object, revents);
|
||||
}
|
||||
|
||||
if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
|
||||
|
||||
/*
|
||||
* if the error events were returned, add POLLIN and POLLOUT
|
||||
* to handle the events at least in one active handler
|
||||
*/
|
||||
|
||||
revents |= POLLIN|POLLOUT;
|
||||
}
|
||||
|
||||
c = ev->data;
|
||||
rev = c->read;
|
||||
wev = c->write;
|
||||
|
||||
rev->active = 0;
|
||||
wev->active = 0;
|
||||
|
||||
if (revents & POLLIN) {
|
||||
rev->ready = 1;
|
||||
rev->available = -1;
|
||||
|
||||
if (flags & NGX_POST_EVENTS) {
|
||||
queue = rev->accept ? &ngx_posted_accept_events
|
||||
: &ngx_posted_events;
|
||||
|
||||
ngx_post_event(rev, queue);
|
||||
|
||||
} else {
|
||||
rev->handler(rev);
|
||||
|
||||
if (ev->closed || ev->instance != instance) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (rev->accept) {
|
||||
if (ngx_use_accept_mutex) {
|
||||
ngx_accept_events = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (port_associate(ep, PORT_SOURCE_FD, c->fd, POLLIN,
|
||||
(void *) ((uintptr_t) ev | ev->instance))
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
|
||||
"port_associate() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (revents & POLLOUT) {
|
||||
wev->ready = 1;
|
||||
|
||||
if (flags & NGX_POST_EVENTS) {
|
||||
ngx_post_event(wev, &ngx_posted_events);
|
||||
|
||||
} else {
|
||||
wev->handler(wev);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
case PORT_SOURCE_USER:
|
||||
|
||||
ev->handler(ev);
|
||||
|
||||
continue;
|
||||
|
||||
default:
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"unexpected eventport object %d",
|
||||
(int) event_list[i].portev_object);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_eventport_create_conf(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_eventport_conf_t *epcf;
|
||||
|
||||
epcf = ngx_palloc(cycle->pool, sizeof(ngx_eventport_conf_t));
|
||||
if (epcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
epcf->events = NGX_CONF_UNSET;
|
||||
|
||||
return epcf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_eventport_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_eventport_conf_t *epcf = conf;
|
||||
|
||||
ngx_conf_init_uint_value(epcf->events, 32);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
#include <ngx_iocp_module.h>
|
||||
|
||||
|
||||
static ngx_int_t ngx_iocp_init(ngx_cycle_t *cycle, ngx_msec_t timer);
|
||||
static ngx_thread_value_t __stdcall ngx_iocp_timer(void *data);
|
||||
static void ngx_iocp_done(ngx_cycle_t *cycle);
|
||||
static ngx_int_t ngx_iocp_add_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t key);
|
||||
static ngx_int_t ngx_iocp_del_connection(ngx_connection_t *c, ngx_uint_t flags);
|
||||
static ngx_int_t ngx_iocp_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags);
|
||||
static void *ngx_iocp_create_conf(ngx_cycle_t *cycle);
|
||||
static char *ngx_iocp_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
|
||||
static ngx_str_t iocp_name = ngx_string("iocp");
|
||||
|
||||
static ngx_command_t ngx_iocp_commands[] = {
|
||||
|
||||
{ ngx_string("iocp_threads"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
0,
|
||||
offsetof(ngx_iocp_conf_t, threads),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("post_acceptex"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
0,
|
||||
offsetof(ngx_iocp_conf_t, post_acceptex),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("acceptex_read"),
|
||||
NGX_EVENT_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
0,
|
||||
offsetof(ngx_iocp_conf_t, acceptex_read),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_event_module_t ngx_iocp_module_ctx = {
|
||||
&iocp_name,
|
||||
ngx_iocp_create_conf, /* create configuration */
|
||||
ngx_iocp_init_conf, /* init configuration */
|
||||
|
||||
{
|
||||
ngx_iocp_add_event, /* add an event */
|
||||
NULL, /* delete an event */
|
||||
NULL, /* enable an event */
|
||||
NULL, /* disable an event */
|
||||
NULL, /* add an connection */
|
||||
ngx_iocp_del_connection, /* delete an connection */
|
||||
NULL, /* trigger a notify */
|
||||
ngx_iocp_process_events, /* process the events */
|
||||
ngx_iocp_init, /* init the events */
|
||||
ngx_iocp_done /* done the events */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ngx_module_t ngx_iocp_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_iocp_module_ctx, /* module context */
|
||||
ngx_iocp_commands, /* module directives */
|
||||
NGX_EVENT_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
ngx_os_io_t ngx_iocp_io = {
|
||||
ngx_overlapped_wsarecv,
|
||||
NULL,
|
||||
ngx_udp_overlapped_wsarecv,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
ngx_overlapped_wsasend_chain,
|
||||
0
|
||||
};
|
||||
|
||||
|
||||
static HANDLE iocp;
|
||||
static ngx_tid_t timer_thread;
|
||||
static ngx_msec_t msec;
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_iocp_init(ngx_cycle_t *cycle, ngx_msec_t timer)
|
||||
{
|
||||
ngx_iocp_conf_t *cf;
|
||||
|
||||
cf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module);
|
||||
|
||||
if (iocp == NULL) {
|
||||
iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0,
|
||||
cf->threads);
|
||||
}
|
||||
|
||||
if (iocp == NULL) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"CreateIoCompletionPort() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_io = ngx_iocp_io;
|
||||
|
||||
ngx_event_actions = ngx_iocp_module_ctx.actions;
|
||||
|
||||
ngx_event_flags = NGX_USE_IOCP_EVENT;
|
||||
|
||||
if (timer == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* The waitable timer could not be used, because
|
||||
* GetQueuedCompletionStatus() does not set a thread to alertable state
|
||||
*/
|
||||
|
||||
if (timer_thread == NULL) {
|
||||
|
||||
msec = timer;
|
||||
|
||||
if (ngx_create_thread(&timer_thread, ngx_iocp_timer, &msec, cycle->log)
|
||||
!= 0)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_event_flags |= NGX_USE_TIMER_EVENT;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_thread_value_t __stdcall
|
||||
ngx_iocp_timer(void *data)
|
||||
{
|
||||
ngx_msec_t timer = *(ngx_msec_t *) data;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
|
||||
"THREAD %p %p", &msec, data);
|
||||
|
||||
for ( ;; ) {
|
||||
Sleep(timer);
|
||||
|
||||
ngx_time_update();
|
||||
#if 1
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__WATCOMC__) || defined(__GNUC__)
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_iocp_done(ngx_cycle_t *cycle)
|
||||
{
|
||||
if (CloseHandle(iocp) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"iocp CloseHandle() failed");
|
||||
}
|
||||
|
||||
iocp = NULL;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_iocp_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t key)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = (ngx_connection_t *) ev->data;
|
||||
|
||||
c->read->active = 1;
|
||||
c->write->active = 1;
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"iocp add: fd:%d k:%ui ov:%p", c->fd, key, &ev->ovlp);
|
||||
|
||||
if (CreateIoCompletionPort((HANDLE) c->fd, iocp, key, 0) == NULL) {
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
|
||||
"CreateIoCompletionPort() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_iocp_del_connection(ngx_connection_t *c, ngx_uint_t flags)
|
||||
{
|
||||
#if 0
|
||||
if (flags & NGX_CLOSE_EVENT) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (CancelIo((HANDLE) c->fd) == 0) {
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, "CancelIo() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_iocp_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
|
||||
{
|
||||
int rc;
|
||||
u_int key;
|
||||
u_long bytes;
|
||||
ngx_err_t err;
|
||||
ngx_msec_t delta;
|
||||
ngx_event_t *ev;
|
||||
ngx_event_ovlp_t *ovlp;
|
||||
|
||||
if (timer == NGX_TIMER_INFINITE) {
|
||||
timer = INFINITE;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "iocp timer: %M", timer);
|
||||
|
||||
rc = GetQueuedCompletionStatus(iocp, &bytes, (PULONG_PTR) &key,
|
||||
(LPOVERLAPPED *) &ovlp, (u_long) timer);
|
||||
|
||||
if (rc == 0) {
|
||||
err = ngx_errno;
|
||||
} else {
|
||||
err = 0;
|
||||
}
|
||||
|
||||
delta = ngx_current_msec;
|
||||
|
||||
if (flags & NGX_UPDATE_TIME) {
|
||||
ngx_time_update();
|
||||
}
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"iocp: %d b:%d k:%d ov:%p", rc, bytes, key, ovlp);
|
||||
|
||||
if (timer != INFINITE) {
|
||||
delta = ngx_current_msec - delta;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"iocp timer: %M, delta: %M", timer, delta);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
if (ovlp == NULL) {
|
||||
if (err != WAIT_TIMEOUT) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
|
||||
"GetQueuedCompletionStatus() failed");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ovlp->error = err;
|
||||
}
|
||||
|
||||
if (ovlp == NULL) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"GetQueuedCompletionStatus() returned no operation");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
ev = ovlp->event;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, err, "iocp event:%p", ev);
|
||||
|
||||
|
||||
if (err == ERROR_NETNAME_DELETED /* the socket was closed */
|
||||
|| err == ERROR_OPERATION_ABORTED /* the operation was canceled */)
|
||||
{
|
||||
|
||||
/*
|
||||
* the WSA_OPERATION_ABORTED completion notification
|
||||
* for a file descriptor that was closed
|
||||
*/
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, err,
|
||||
"iocp: aborted event %p", ev);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
|
||||
"GetQueuedCompletionStatus() returned operation error");
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
|
||||
case NGX_IOCP_ACCEPT:
|
||||
if (bytes) {
|
||||
ev->ready = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_IOCP_IO:
|
||||
ev->complete = 1;
|
||||
ev->ready = 1;
|
||||
break;
|
||||
|
||||
case NGX_IOCP_CONNECT:
|
||||
ev->ready = 1;
|
||||
}
|
||||
|
||||
ev->available = bytes;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"iocp event handler: %p", ev->handler);
|
||||
|
||||
ev->handler(ev);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_iocp_create_conf(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_iocp_conf_t *cf;
|
||||
|
||||
cf = ngx_palloc(cycle->pool, sizeof(ngx_iocp_conf_t));
|
||||
if (cf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cf->threads = NGX_CONF_UNSET;
|
||||
cf->post_acceptex = NGX_CONF_UNSET;
|
||||
cf->acceptex_read = NGX_CONF_UNSET;
|
||||
|
||||
return cf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_iocp_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_iocp_conf_t *cf = conf;
|
||||
|
||||
ngx_conf_init_value(cf->threads, 0);
|
||||
ngx_conf_init_value(cf->post_acceptex, 10);
|
||||
ngx_conf_init_value(cf->acceptex_read, 1);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_IOCP_MODULE_H_INCLUDED_
|
||||
#define _NGX_IOCP_MODULE_H_INCLUDED_
|
||||
|
||||
|
||||
typedef struct {
|
||||
int threads;
|
||||
int post_acceptex;
|
||||
int acceptex_read;
|
||||
} ngx_iocp_conf_t;
|
||||
|
||||
|
||||
extern ngx_module_t ngx_iocp_module;
|
||||
|
||||
|
||||
#endif /* _NGX_IOCP_MODULE_H_INCLUDED_ */
|
||||
@@ -0,0 +1,722 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t changes;
|
||||
ngx_uint_t events;
|
||||
} ngx_kqueue_conf_t;
|
||||
|
||||
|
||||
static ngx_int_t ngx_kqueue_init(ngx_cycle_t *cycle, ngx_msec_t timer);
|
||||
#ifdef EVFILT_USER
|
||||
static ngx_int_t ngx_kqueue_notify_init(ngx_log_t *log);
|
||||
#endif
|
||||
static void ngx_kqueue_done(ngx_cycle_t *cycle);
|
||||
static ngx_int_t ngx_kqueue_add_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_kqueue_del_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_kqueue_set_event(ngx_event_t *ev, ngx_int_t filter,
|
||||
ngx_uint_t flags);
|
||||
#ifdef EVFILT_USER
|
||||
static ngx_int_t ngx_kqueue_notify(ngx_event_handler_pt handler);
|
||||
#endif
|
||||
static ngx_int_t ngx_kqueue_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags);
|
||||
static ngx_inline void ngx_kqueue_dump_event(ngx_log_t *log,
|
||||
struct kevent *kev);
|
||||
|
||||
static void *ngx_kqueue_create_conf(ngx_cycle_t *cycle);
|
||||
static char *ngx_kqueue_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
|
||||
int ngx_kqueue = -1;
|
||||
|
||||
static struct kevent *change_list;
|
||||
static struct kevent *event_list;
|
||||
static ngx_uint_t max_changes, nchanges, nevents;
|
||||
|
||||
#ifdef EVFILT_USER
|
||||
static ngx_event_t notify_event;
|
||||
static struct kevent notify_kev;
|
||||
#endif
|
||||
|
||||
|
||||
static ngx_str_t kqueue_name = ngx_string("kqueue");
|
||||
|
||||
static ngx_command_t ngx_kqueue_commands[] = {
|
||||
|
||||
{ ngx_string("kqueue_changes"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
0,
|
||||
offsetof(ngx_kqueue_conf_t, changes),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("kqueue_events"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
0,
|
||||
offsetof(ngx_kqueue_conf_t, events),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_event_module_t ngx_kqueue_module_ctx = {
|
||||
&kqueue_name,
|
||||
ngx_kqueue_create_conf, /* create configuration */
|
||||
ngx_kqueue_init_conf, /* init configuration */
|
||||
|
||||
{
|
||||
ngx_kqueue_add_event, /* add an event */
|
||||
ngx_kqueue_del_event, /* delete an event */
|
||||
ngx_kqueue_add_event, /* enable an event */
|
||||
ngx_kqueue_del_event, /* disable an event */
|
||||
NULL, /* add an connection */
|
||||
NULL, /* delete an connection */
|
||||
#ifdef EVFILT_USER
|
||||
ngx_kqueue_notify, /* trigger a notify */
|
||||
#else
|
||||
NULL, /* trigger a notify */
|
||||
#endif
|
||||
ngx_kqueue_process_events, /* process the events */
|
||||
ngx_kqueue_init, /* init the events */
|
||||
ngx_kqueue_done /* done the events */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ngx_module_t ngx_kqueue_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_kqueue_module_ctx, /* module context */
|
||||
ngx_kqueue_commands, /* module directives */
|
||||
NGX_EVENT_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_kqueue_init(ngx_cycle_t *cycle, ngx_msec_t timer)
|
||||
{
|
||||
ngx_kqueue_conf_t *kcf;
|
||||
struct timespec ts;
|
||||
#if (NGX_HAVE_TIMER_EVENT)
|
||||
struct kevent kev;
|
||||
#endif
|
||||
|
||||
kcf = ngx_event_get_conf(cycle->conf_ctx, ngx_kqueue_module);
|
||||
|
||||
if (ngx_kqueue == -1) {
|
||||
ngx_kqueue = kqueue();
|
||||
|
||||
if (ngx_kqueue == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
||||
"kqueue() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#ifdef EVFILT_USER
|
||||
if (ngx_kqueue_notify_init(cycle->log) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (max_changes < kcf->changes) {
|
||||
if (nchanges) {
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 0;
|
||||
|
||||
if (kevent(ngx_kqueue, change_list, (int) nchanges, NULL, 0, &ts)
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"kevent() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
nchanges = 0;
|
||||
}
|
||||
|
||||
if (change_list) {
|
||||
ngx_free(change_list);
|
||||
}
|
||||
|
||||
change_list = ngx_alloc(kcf->changes * sizeof(struct kevent),
|
||||
cycle->log);
|
||||
if (change_list == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
max_changes = kcf->changes;
|
||||
|
||||
if (nevents < kcf->events) {
|
||||
if (event_list) {
|
||||
ngx_free(event_list);
|
||||
}
|
||||
|
||||
event_list = ngx_alloc(kcf->events * sizeof(struct kevent), cycle->log);
|
||||
if (event_list == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_event_flags = NGX_USE_ONESHOT_EVENT
|
||||
|NGX_USE_KQUEUE_EVENT
|
||||
|NGX_USE_VNODE_EVENT;
|
||||
|
||||
#if (NGX_HAVE_TIMER_EVENT)
|
||||
|
||||
if (timer) {
|
||||
kev.ident = 0;
|
||||
kev.filter = EVFILT_TIMER;
|
||||
kev.flags = EV_ADD|EV_ENABLE;
|
||||
kev.fflags = 0;
|
||||
kev.data = timer;
|
||||
kev.udata = 0;
|
||||
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 0;
|
||||
|
||||
if (kevent(ngx_kqueue, &kev, 1, NULL, 0, &ts) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"kevent(EVFILT_TIMER) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_event_flags |= NGX_USE_TIMER_EVENT;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_CLEAR_EVENT)
|
||||
ngx_event_flags |= NGX_USE_CLEAR_EVENT;
|
||||
#else
|
||||
ngx_event_flags |= NGX_USE_LEVEL_EVENT;
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_LOWAT_EVENT)
|
||||
ngx_event_flags |= NGX_USE_LOWAT_EVENT;
|
||||
#endif
|
||||
|
||||
nevents = kcf->events;
|
||||
|
||||
ngx_io = ngx_os_io;
|
||||
|
||||
ngx_event_actions = ngx_kqueue_module_ctx.actions;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#ifdef EVFILT_USER
|
||||
|
||||
static ngx_int_t
|
||||
ngx_kqueue_notify_init(ngx_log_t *log)
|
||||
{
|
||||
notify_kev.ident = 0;
|
||||
notify_kev.filter = EVFILT_USER;
|
||||
notify_kev.data = 0;
|
||||
notify_kev.flags = EV_ADD|EV_CLEAR;
|
||||
notify_kev.fflags = 0;
|
||||
notify_kev.udata = 0;
|
||||
|
||||
if (kevent(ngx_kqueue, ¬ify_kev, 1, NULL, 0, NULL) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
|
||||
"kevent(EVFILT_USER, EV_ADD) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
notify_event.active = 1;
|
||||
notify_event.log = log;
|
||||
|
||||
notify_kev.flags = 0;
|
||||
notify_kev.fflags = NOTE_TRIGGER;
|
||||
notify_kev.udata = NGX_KQUEUE_UDATA_T ((uintptr_t) ¬ify_event);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static void
|
||||
ngx_kqueue_done(ngx_cycle_t *cycle)
|
||||
{
|
||||
if (close(ngx_kqueue) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"kqueue close() failed");
|
||||
}
|
||||
|
||||
ngx_kqueue = -1;
|
||||
|
||||
ngx_free(change_list);
|
||||
ngx_free(event_list);
|
||||
|
||||
change_list = NULL;
|
||||
event_list = NULL;
|
||||
max_changes = 0;
|
||||
nchanges = 0;
|
||||
nevents = 0;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_kqueue_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
#if 0
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
#endif
|
||||
|
||||
ev->active = 1;
|
||||
ev->disabled = 0;
|
||||
ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
|
||||
|
||||
#if 0
|
||||
|
||||
if (ev->index < nchanges
|
||||
&& ((uintptr_t) change_list[ev->index].udata & (uintptr_t) ~1)
|
||||
== (uintptr_t) ev)
|
||||
{
|
||||
if (change_list[ev->index].flags == EV_DISABLE) {
|
||||
|
||||
/*
|
||||
* if the EV_DISABLE is still not passed to a kernel
|
||||
* we will not pass it
|
||||
*/
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"kevent activated: %d: ft:%i",
|
||||
ngx_event_ident(ev->data), event);
|
||||
|
||||
if (ev->index < --nchanges) {
|
||||
e = (ngx_event_t *)
|
||||
((uintptr_t) change_list[nchanges].udata & (uintptr_t) ~1);
|
||||
change_list[ev->index] = change_list[nchanges];
|
||||
e->index = ev->index;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"previous event on #%d were not passed in kernel", c->fd);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
rc = ngx_kqueue_set_event(ev, event, EV_ADD|EV_ENABLE|flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_kqueue_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_event_t *e;
|
||||
|
||||
ev->active = 0;
|
||||
ev->disabled = 0;
|
||||
|
||||
if (ev->index < nchanges
|
||||
&& ((uintptr_t) change_list[ev->index].udata & (uintptr_t) ~1)
|
||||
== (uintptr_t) ev)
|
||||
{
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"kevent deleted: %d: ft:%i",
|
||||
ngx_event_ident(ev->data), event);
|
||||
|
||||
/* if the event is still not passed to a kernel we will not pass it */
|
||||
|
||||
nchanges--;
|
||||
|
||||
if (ev->index < nchanges) {
|
||||
e = (ngx_event_t *)
|
||||
((uintptr_t) change_list[nchanges].udata & (uintptr_t) ~1);
|
||||
change_list[ev->index] = change_list[nchanges];
|
||||
e->index = ev->index;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* when the file descriptor is closed the kqueue automatically deletes
|
||||
* its filters so we do not need to delete explicitly the event
|
||||
* before the closing the file descriptor.
|
||||
*/
|
||||
|
||||
if (flags & NGX_CLOSE_EVENT) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (flags & NGX_DISABLE_EVENT) {
|
||||
ev->disabled = 1;
|
||||
|
||||
} else {
|
||||
flags |= EV_DELETE;
|
||||
}
|
||||
|
||||
rc = ngx_kqueue_set_event(ev, event, flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_kqueue_set_event(ngx_event_t *ev, ngx_int_t filter, ngx_uint_t flags)
|
||||
{
|
||||
struct kevent *kev;
|
||||
struct timespec ts;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"kevent set event: %d: ft:%i fl:%04Xi",
|
||||
c->fd, filter, flags);
|
||||
|
||||
if (nchanges >= max_changes) {
|
||||
ngx_log_error(NGX_LOG_WARN, ev->log, 0,
|
||||
"kqueue change list is filled up");
|
||||
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 0;
|
||||
|
||||
if (kevent(ngx_kqueue, change_list, (int) nchanges, NULL, 0, &ts)
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "kevent() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
nchanges = 0;
|
||||
}
|
||||
|
||||
kev = &change_list[nchanges];
|
||||
|
||||
kev->ident = c->fd;
|
||||
kev->filter = (short) filter;
|
||||
kev->flags = (u_short) flags;
|
||||
kev->udata = NGX_KQUEUE_UDATA_T ((uintptr_t) ev | ev->instance);
|
||||
|
||||
if (filter == EVFILT_VNODE) {
|
||||
kev->fflags = NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND
|
||||
|NOTE_ATTRIB|NOTE_RENAME
|
||||
#if (__FreeBSD__ == 4 && __FreeBSD_version >= 430000) \
|
||||
|| __FreeBSD_version >= 500018
|
||||
|NOTE_REVOKE
|
||||
#endif
|
||||
;
|
||||
kev->data = 0;
|
||||
|
||||
} else {
|
||||
#if (NGX_HAVE_LOWAT_EVENT)
|
||||
if (flags & NGX_LOWAT_EVENT) {
|
||||
kev->fflags = NOTE_LOWAT;
|
||||
kev->data = ev->available;
|
||||
|
||||
} else {
|
||||
kev->fflags = 0;
|
||||
kev->data = 0;
|
||||
}
|
||||
#else
|
||||
kev->fflags = 0;
|
||||
kev->data = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
ev->index = nchanges;
|
||||
nchanges++;
|
||||
|
||||
if (flags & NGX_FLUSH_EVENT) {
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 0;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "kevent flush");
|
||||
|
||||
if (kevent(ngx_kqueue, change_list, (int) nchanges, NULL, 0, &ts)
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "kevent() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
nchanges = 0;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#ifdef EVFILT_USER
|
||||
|
||||
static ngx_int_t
|
||||
ngx_kqueue_notify(ngx_event_handler_pt handler)
|
||||
{
|
||||
notify_event.handler = handler;
|
||||
|
||||
if (kevent(ngx_kqueue, ¬ify_kev, 1, NULL, 0, NULL) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, notify_event.log, ngx_errno,
|
||||
"kevent(EVFILT_USER, NOTE_TRIGGER) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_kqueue_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags)
|
||||
{
|
||||
int events, n;
|
||||
ngx_int_t i, instance;
|
||||
ngx_uint_t level;
|
||||
ngx_err_t err;
|
||||
ngx_event_t *ev;
|
||||
ngx_queue_t *queue;
|
||||
struct timespec ts, *tp;
|
||||
|
||||
n = (int) nchanges;
|
||||
nchanges = 0;
|
||||
|
||||
if (timer == NGX_TIMER_INFINITE) {
|
||||
tp = NULL;
|
||||
|
||||
} else {
|
||||
|
||||
ts.tv_sec = timer / 1000;
|
||||
ts.tv_nsec = (timer % 1000) * 1000000;
|
||||
|
||||
/*
|
||||
* 64-bit Darwin kernel has the bug: kernel level ts.tv_nsec is
|
||||
* the int32_t while user level ts.tv_nsec is the long (64-bit),
|
||||
* so on the big endian PowerPC all nanoseconds are lost.
|
||||
*/
|
||||
|
||||
#if (NGX_DARWIN_KEVENT_BUG)
|
||||
ts.tv_nsec <<= 32;
|
||||
#endif
|
||||
|
||||
tp = &ts;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"kevent timer: %M, changes: %d", timer, n);
|
||||
|
||||
events = kevent(ngx_kqueue, change_list, n, event_list, (int) nevents, tp);
|
||||
|
||||
err = (events == -1) ? ngx_errno : 0;
|
||||
|
||||
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
|
||||
ngx_time_update();
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"kevent events: %d", events);
|
||||
|
||||
if (err) {
|
||||
if (err == NGX_EINTR) {
|
||||
|
||||
if (ngx_event_timer_alarm) {
|
||||
ngx_event_timer_alarm = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
level = NGX_LOG_INFO;
|
||||
|
||||
} else {
|
||||
level = NGX_LOG_ALERT;
|
||||
}
|
||||
|
||||
ngx_log_error(level, cycle->log, err, "kevent() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (events == 0) {
|
||||
if (timer != NGX_TIMER_INFINITE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"kevent() returned no events without timeout");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; i < events; i++) {
|
||||
|
||||
ngx_kqueue_dump_event(cycle->log, &event_list[i]);
|
||||
|
||||
if (event_list[i].flags & EV_ERROR) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, event_list[i].data,
|
||||
"kevent() error on %d filter:%d flags:%04Xd",
|
||||
(int) event_list[i].ident, event_list[i].filter,
|
||||
event_list[i].flags);
|
||||
continue;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_TIMER_EVENT)
|
||||
|
||||
if (event_list[i].filter == EVFILT_TIMER) {
|
||||
ngx_time_update();
|
||||
continue;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
ev = (ngx_event_t *) event_list[i].udata;
|
||||
|
||||
switch (event_list[i].filter) {
|
||||
|
||||
case EVFILT_READ:
|
||||
case EVFILT_WRITE:
|
||||
|
||||
instance = (uintptr_t) ev & 1;
|
||||
ev = (ngx_event_t *) ((uintptr_t) ev & (uintptr_t) ~1);
|
||||
|
||||
if (ev->closed || ev->instance != instance) {
|
||||
|
||||
/*
|
||||
* the stale event from a file descriptor
|
||||
* that was just closed in this iteration
|
||||
*/
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"kevent: stale event %p", ev);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev->log && (ev->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
|
||||
ngx_kqueue_dump_event(ev->log, &event_list[i]);
|
||||
}
|
||||
|
||||
if (ev->oneshot) {
|
||||
ev->active = 0;
|
||||
}
|
||||
|
||||
ev->available = event_list[i].data;
|
||||
|
||||
if (event_list[i].flags & EV_EOF) {
|
||||
ev->pending_eof = 1;
|
||||
ev->kq_errno = event_list[i].fflags;
|
||||
}
|
||||
|
||||
ev->ready = 1;
|
||||
|
||||
break;
|
||||
|
||||
case EVFILT_VNODE:
|
||||
ev->kq_vnode = 1;
|
||||
|
||||
break;
|
||||
|
||||
case EVFILT_AIO:
|
||||
ev->complete = 1;
|
||||
ev->ready = 1;
|
||||
|
||||
break;
|
||||
|
||||
#ifdef EVFILT_USER
|
||||
case EVFILT_USER:
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"unexpected kevent() filter %d",
|
||||
event_list[i].filter);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flags & NGX_POST_EVENTS) {
|
||||
queue = ev->accept ? &ngx_posted_accept_events
|
||||
: &ngx_posted_events;
|
||||
|
||||
ngx_post_event(ev, queue);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ev->handler(ev);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline void
|
||||
ngx_kqueue_dump_event(ngx_log_t *log, struct kevent *kev)
|
||||
{
|
||||
if (kev->ident > 0x8000000 && kev->ident != (unsigned) -1) {
|
||||
ngx_log_debug6(NGX_LOG_DEBUG_EVENT, log, 0,
|
||||
"kevent: %p: ft:%d fl:%04Xd ff:%08Xd d:%d ud:%p",
|
||||
(void *) kev->ident, kev->filter,
|
||||
kev->flags, kev->fflags,
|
||||
(int) kev->data, kev->udata);
|
||||
|
||||
} else {
|
||||
ngx_log_debug6(NGX_LOG_DEBUG_EVENT, log, 0,
|
||||
"kevent: %d: ft:%d fl:%04Xd ff:%08Xd d:%d ud:%p",
|
||||
(int) kev->ident, kev->filter,
|
||||
kev->flags, kev->fflags,
|
||||
(int) kev->data, kev->udata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_kqueue_create_conf(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_kqueue_conf_t *kcf;
|
||||
|
||||
kcf = ngx_palloc(cycle->pool, sizeof(ngx_kqueue_conf_t));
|
||||
if (kcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
kcf->changes = NGX_CONF_UNSET;
|
||||
kcf->events = NGX_CONF_UNSET;
|
||||
|
||||
return kcf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_kqueue_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_kqueue_conf_t *kcf = conf;
|
||||
|
||||
ngx_conf_init_uint_value(kcf->changes, 512);
|
||||
ngx_conf_init_uint_value(kcf->events, 512);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
static ngx_int_t ngx_poll_init(ngx_cycle_t *cycle, ngx_msec_t timer);
|
||||
static void ngx_poll_done(ngx_cycle_t *cycle);
|
||||
static ngx_int_t ngx_poll_add_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_poll_del_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_poll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags);
|
||||
static char *ngx_poll_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
|
||||
static struct pollfd *event_list;
|
||||
static ngx_uint_t nevents;
|
||||
|
||||
|
||||
static ngx_str_t poll_name = ngx_string("poll");
|
||||
|
||||
static ngx_event_module_t ngx_poll_module_ctx = {
|
||||
&poll_name,
|
||||
NULL, /* create configuration */
|
||||
ngx_poll_init_conf, /* init configuration */
|
||||
|
||||
{
|
||||
ngx_poll_add_event, /* add an event */
|
||||
ngx_poll_del_event, /* delete an event */
|
||||
ngx_poll_add_event, /* enable an event */
|
||||
ngx_poll_del_event, /* disable an event */
|
||||
NULL, /* add an connection */
|
||||
NULL, /* delete an connection */
|
||||
NULL, /* trigger a notify */
|
||||
ngx_poll_process_events, /* process the events */
|
||||
ngx_poll_init, /* init the events */
|
||||
ngx_poll_done /* done the events */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ngx_module_t ngx_poll_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_poll_module_ctx, /* module context */
|
||||
NULL, /* module directives */
|
||||
NGX_EVENT_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_poll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
|
||||
{
|
||||
struct pollfd *list;
|
||||
|
||||
if (event_list == NULL) {
|
||||
nevents = 0;
|
||||
}
|
||||
|
||||
if (ngx_process >= NGX_PROCESS_WORKER
|
||||
|| cycle->old_cycle == NULL
|
||||
|| cycle->old_cycle->connection_n < cycle->connection_n)
|
||||
{
|
||||
list = ngx_alloc(sizeof(struct pollfd) * cycle->connection_n,
|
||||
cycle->log);
|
||||
if (list == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (event_list) {
|
||||
ngx_memcpy(list, event_list, sizeof(struct pollfd) * nevents);
|
||||
ngx_free(event_list);
|
||||
}
|
||||
|
||||
event_list = list;
|
||||
}
|
||||
|
||||
ngx_io = ngx_os_io;
|
||||
|
||||
ngx_event_actions = ngx_poll_module_ctx.actions;
|
||||
|
||||
ngx_event_flags = NGX_USE_LEVEL_EVENT|NGX_USE_FD_EVENT;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_poll_done(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_free(event_list);
|
||||
|
||||
event_list = NULL;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_poll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ev->active = 1;
|
||||
|
||||
if (ev->index != NGX_INVALID_INDEX) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"poll event fd:%d ev:%i is already set", c->fd, event);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
e = c->write;
|
||||
#if (NGX_READ_EVENT != POLLIN)
|
||||
event = POLLIN;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
e = c->read;
|
||||
#if (NGX_WRITE_EVENT != POLLOUT)
|
||||
event = POLLOUT;
|
||||
#endif
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"poll add event: fd:%d ev:%i", c->fd, event);
|
||||
|
||||
if (e == NULL || e->index == NGX_INVALID_INDEX) {
|
||||
event_list[nevents].fd = c->fd;
|
||||
event_list[nevents].events = (short) event;
|
||||
event_list[nevents].revents = 0;
|
||||
|
||||
ev->index = nevents;
|
||||
nevents++;
|
||||
|
||||
} else {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"poll add index: %i", e->index);
|
||||
|
||||
event_list[e->index].events |= (short) event;
|
||||
ev->index = e->index;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_poll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ev->active = 0;
|
||||
|
||||
if (ev->index == NGX_INVALID_INDEX) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"poll event fd:%d ev:%i is already deleted",
|
||||
c->fd, event);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
e = c->write;
|
||||
#if (NGX_READ_EVENT != POLLIN)
|
||||
event = POLLIN;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
e = c->read;
|
||||
#if (NGX_WRITE_EVENT != POLLOUT)
|
||||
event = POLLOUT;
|
||||
#endif
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"poll del event: fd:%d ev:%i", c->fd, event);
|
||||
|
||||
if (e == NULL || e->index == NGX_INVALID_INDEX) {
|
||||
nevents--;
|
||||
|
||||
if (ev->index < nevents) {
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"index: copy event %ui to %i", nevents, ev->index);
|
||||
|
||||
event_list[ev->index] = event_list[nevents];
|
||||
|
||||
c = ngx_cycle->files[event_list[nevents].fd];
|
||||
|
||||
if (c->fd == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"unexpected last event");
|
||||
|
||||
} else {
|
||||
if (c->read->index == nevents) {
|
||||
c->read->index = ev->index;
|
||||
}
|
||||
|
||||
if (c->write->index == nevents) {
|
||||
c->write->index = ev->index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"poll del index: %i", e->index);
|
||||
|
||||
event_list[e->index].events &= (short) ~event;
|
||||
}
|
||||
|
||||
ev->index = NGX_INVALID_INDEX;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_poll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
|
||||
{
|
||||
int ready, revents;
|
||||
ngx_err_t err;
|
||||
ngx_uint_t i, found, level;
|
||||
ngx_event_t *ev;
|
||||
ngx_queue_t *queue;
|
||||
ngx_connection_t *c;
|
||||
|
||||
/* NGX_TIMER_INFINITE == INFTIM */
|
||||
|
||||
#if (NGX_DEBUG0)
|
||||
if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
|
||||
for (i = 0; i < nevents; i++) {
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"poll: %ui: fd:%d ev:%04Xd",
|
||||
i, event_list[i].fd, event_list[i].events);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "poll timer: %M", timer);
|
||||
|
||||
ready = poll(event_list, (u_int) nevents, (int) timer);
|
||||
|
||||
err = (ready == -1) ? ngx_errno : 0;
|
||||
|
||||
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
|
||||
ngx_time_update();
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"poll ready %d of %ui", ready, nevents);
|
||||
|
||||
if (err) {
|
||||
if (err == NGX_EINTR) {
|
||||
|
||||
if (ngx_event_timer_alarm) {
|
||||
ngx_event_timer_alarm = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
level = NGX_LOG_INFO;
|
||||
|
||||
} else {
|
||||
level = NGX_LOG_ALERT;
|
||||
}
|
||||
|
||||
ngx_log_error(level, cycle->log, err, "poll() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ready == 0) {
|
||||
if (timer != NGX_TIMER_INFINITE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"poll() returned no events without timeout");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; i < nevents && ready; i++) {
|
||||
|
||||
revents = event_list[i].revents;
|
||||
|
||||
#if 1
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"poll: %ui: fd:%d ev:%04Xd rev:%04Xd",
|
||||
i, event_list[i].fd, event_list[i].events, revents);
|
||||
#else
|
||||
if (revents) {
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"poll: %ui: fd:%d ev:%04Xd rev:%04Xd",
|
||||
i, event_list[i].fd, event_list[i].events, revents);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (revents & POLLNVAL) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"poll() error fd:%d ev:%04Xd rev:%04Xd",
|
||||
event_list[i].fd, event_list[i].events, revents);
|
||||
}
|
||||
|
||||
if (revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"strange poll() events fd:%d ev:%04Xd rev:%04Xd",
|
||||
event_list[i].fd, event_list[i].events, revents);
|
||||
}
|
||||
|
||||
if (event_list[i].fd == -1) {
|
||||
/*
|
||||
* the disabled event, a workaround for our possible bug,
|
||||
* see the comment below
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
c = ngx_cycle->files[event_list[i].fd];
|
||||
|
||||
if (c->fd == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "unexpected event");
|
||||
|
||||
/*
|
||||
* it is certainly our fault and it should be investigated,
|
||||
* in the meantime we disable this event to avoid a CPU spinning
|
||||
*/
|
||||
|
||||
if (i == nevents - 1) {
|
||||
nevents--;
|
||||
} else {
|
||||
event_list[i].fd = -1;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
|
||||
|
||||
/*
|
||||
* if the error events were returned, add POLLIN and POLLOUT
|
||||
* to handle the events at least in one active handler
|
||||
*/
|
||||
|
||||
revents |= POLLIN|POLLOUT;
|
||||
}
|
||||
|
||||
found = 0;
|
||||
|
||||
if ((revents & POLLIN) && c->read->active) {
|
||||
found = 1;
|
||||
|
||||
ev = c->read;
|
||||
ev->ready = 1;
|
||||
ev->available = -1;
|
||||
|
||||
queue = ev->accept ? &ngx_posted_accept_events
|
||||
: &ngx_posted_events;
|
||||
|
||||
ngx_post_event(ev, queue);
|
||||
}
|
||||
|
||||
if ((revents & POLLOUT) && c->write->active) {
|
||||
found = 1;
|
||||
|
||||
ev = c->write;
|
||||
ev->ready = 1;
|
||||
|
||||
ngx_post_event(ev, &ngx_posted_events);
|
||||
}
|
||||
|
||||
if (found) {
|
||||
ready--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ready != 0) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "poll ready != events");
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_poll_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_event_conf_t *ecf;
|
||||
|
||||
ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
|
||||
|
||||
if (ecf->use != ngx_poll_module.ctx_index) {
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
static ngx_int_t ngx_select_init(ngx_cycle_t *cycle, ngx_msec_t timer);
|
||||
static void ngx_select_done(ngx_cycle_t *cycle);
|
||||
static ngx_int_t ngx_select_add_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_select_del_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags);
|
||||
static void ngx_select_repair_fd_sets(ngx_cycle_t *cycle);
|
||||
static char *ngx_select_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
|
||||
static fd_set master_read_fd_set;
|
||||
static fd_set master_write_fd_set;
|
||||
static fd_set work_read_fd_set;
|
||||
static fd_set work_write_fd_set;
|
||||
|
||||
static ngx_int_t max_fd;
|
||||
static ngx_uint_t nevents;
|
||||
|
||||
static ngx_event_t **event_index;
|
||||
|
||||
|
||||
static ngx_str_t select_name = ngx_string("select");
|
||||
|
||||
static ngx_event_module_t ngx_select_module_ctx = {
|
||||
&select_name,
|
||||
NULL, /* create configuration */
|
||||
ngx_select_init_conf, /* init configuration */
|
||||
|
||||
{
|
||||
ngx_select_add_event, /* add an event */
|
||||
ngx_select_del_event, /* delete an event */
|
||||
ngx_select_add_event, /* enable an event */
|
||||
ngx_select_del_event, /* disable an event */
|
||||
NULL, /* add an connection */
|
||||
NULL, /* delete an connection */
|
||||
NULL, /* trigger a notify */
|
||||
ngx_select_process_events, /* process the events */
|
||||
ngx_select_init, /* init the events */
|
||||
ngx_select_done /* done the events */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ngx_module_t ngx_select_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_select_module_ctx, /* module context */
|
||||
NULL, /* module directives */
|
||||
NGX_EVENT_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_select_init(ngx_cycle_t *cycle, ngx_msec_t timer)
|
||||
{
|
||||
ngx_event_t **index;
|
||||
|
||||
if (event_index == NULL) {
|
||||
FD_ZERO(&master_read_fd_set);
|
||||
FD_ZERO(&master_write_fd_set);
|
||||
nevents = 0;
|
||||
}
|
||||
|
||||
if (ngx_process >= NGX_PROCESS_WORKER
|
||||
|| cycle->old_cycle == NULL
|
||||
|| cycle->old_cycle->connection_n < cycle->connection_n)
|
||||
{
|
||||
index = ngx_alloc(sizeof(ngx_event_t *) * 2 * cycle->connection_n,
|
||||
cycle->log);
|
||||
if (index == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (event_index) {
|
||||
ngx_memcpy(index, event_index, sizeof(ngx_event_t *) * nevents);
|
||||
ngx_free(event_index);
|
||||
}
|
||||
|
||||
event_index = index;
|
||||
}
|
||||
|
||||
ngx_io = ngx_os_io;
|
||||
|
||||
ngx_event_actions = ngx_select_module_ctx.actions;
|
||||
|
||||
ngx_event_flags = NGX_USE_LEVEL_EVENT;
|
||||
|
||||
max_fd = -1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_select_done(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_free(event_index);
|
||||
|
||||
event_index = NULL;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_select_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"select add event fd:%d ev:%i", c->fd, event);
|
||||
|
||||
if (ev->index != NGX_INVALID_INDEX) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"select event fd:%d ev:%i is already set", c->fd, event);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if ((event == NGX_READ_EVENT && ev->write)
|
||||
|| (event == NGX_WRITE_EVENT && !ev->write))
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"invalid select %s event fd:%d ev:%i",
|
||||
ev->write ? "write" : "read", c->fd, event);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
FD_SET(c->fd, &master_read_fd_set);
|
||||
|
||||
} else if (event == NGX_WRITE_EVENT) {
|
||||
FD_SET(c->fd, &master_write_fd_set);
|
||||
}
|
||||
|
||||
if (max_fd != -1 && max_fd < c->fd) {
|
||||
max_fd = c->fd;
|
||||
}
|
||||
|
||||
ev->active = 1;
|
||||
|
||||
event_index[nevents] = ev;
|
||||
ev->index = nevents;
|
||||
nevents++;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_select_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ev->active = 0;
|
||||
|
||||
if (ev->index == NGX_INVALID_INDEX) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"select del event fd:%d ev:%i", c->fd, event);
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
FD_CLR(c->fd, &master_read_fd_set);
|
||||
|
||||
} else if (event == NGX_WRITE_EVENT) {
|
||||
FD_CLR(c->fd, &master_write_fd_set);
|
||||
}
|
||||
|
||||
if (max_fd == c->fd) {
|
||||
max_fd = -1;
|
||||
}
|
||||
|
||||
if (ev->index < --nevents) {
|
||||
e = event_index[nevents];
|
||||
event_index[ev->index] = e;
|
||||
e->index = ev->index;
|
||||
}
|
||||
|
||||
ev->index = NGX_INVALID_INDEX;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags)
|
||||
{
|
||||
int ready, nready;
|
||||
ngx_err_t err;
|
||||
ngx_uint_t i, found;
|
||||
ngx_event_t *ev;
|
||||
ngx_queue_t *queue;
|
||||
struct timeval tv, *tp;
|
||||
ngx_connection_t *c;
|
||||
|
||||
if (max_fd == -1) {
|
||||
for (i = 0; i < nevents; i++) {
|
||||
c = event_index[i]->data;
|
||||
if (max_fd < c->fd) {
|
||||
max_fd = c->fd;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"change max_fd: %i", max_fd);
|
||||
}
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
|
||||
for (i = 0; i < nevents; i++) {
|
||||
ev = event_index[i];
|
||||
c = ev->data;
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select event: fd:%d wr:%d", c->fd, ev->write);
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"max_fd: %i", max_fd);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (timer == NGX_TIMER_INFINITE) {
|
||||
tp = NULL;
|
||||
|
||||
} else {
|
||||
tv.tv_sec = (long) (timer / 1000);
|
||||
tv.tv_usec = (long) ((timer % 1000) * 1000);
|
||||
tp = &tv;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select timer: %M", timer);
|
||||
|
||||
work_read_fd_set = master_read_fd_set;
|
||||
work_write_fd_set = master_write_fd_set;
|
||||
|
||||
ready = select(max_fd + 1, &work_read_fd_set, &work_write_fd_set, NULL, tp);
|
||||
|
||||
err = (ready == -1) ? ngx_errno : 0;
|
||||
|
||||
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
|
||||
ngx_time_update();
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select ready %d", ready);
|
||||
|
||||
if (err) {
|
||||
ngx_uint_t level;
|
||||
|
||||
if (err == NGX_EINTR) {
|
||||
|
||||
if (ngx_event_timer_alarm) {
|
||||
ngx_event_timer_alarm = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
level = NGX_LOG_INFO;
|
||||
|
||||
} else {
|
||||
level = NGX_LOG_ALERT;
|
||||
}
|
||||
|
||||
ngx_log_error(level, cycle->log, err, "select() failed");
|
||||
|
||||
if (err == NGX_EBADF) {
|
||||
ngx_select_repair_fd_sets(cycle);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ready == 0) {
|
||||
if (timer != NGX_TIMER_INFINITE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"select() returned no events without timeout");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
nready = 0;
|
||||
|
||||
for (i = 0; i < nevents; i++) {
|
||||
ev = event_index[i];
|
||||
c = ev->data;
|
||||
found = 0;
|
||||
|
||||
if (ev->write) {
|
||||
if (FD_ISSET(c->fd, &work_write_fd_set)) {
|
||||
found = 1;
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select write %d", c->fd);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (FD_ISSET(c->fd, &work_read_fd_set)) {
|
||||
found = 1;
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select read %d", c->fd);
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
ev->ready = 1;
|
||||
ev->available = -1;
|
||||
|
||||
queue = ev->accept ? &ngx_posted_accept_events
|
||||
: &ngx_posted_events;
|
||||
|
||||
ngx_post_event(ev, queue);
|
||||
|
||||
nready++;
|
||||
}
|
||||
}
|
||||
|
||||
if (ready != nready) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"select ready != events: %d:%d", ready, nready);
|
||||
|
||||
ngx_select_repair_fd_sets(cycle);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_select_repair_fd_sets(ngx_cycle_t *cycle)
|
||||
{
|
||||
int n;
|
||||
socklen_t len;
|
||||
ngx_err_t err;
|
||||
ngx_socket_t s;
|
||||
|
||||
for (s = 0; s <= max_fd; s++) {
|
||||
|
||||
if (FD_ISSET(s, &master_read_fd_set) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
len = sizeof(int);
|
||||
|
||||
if (getsockopt(s, SOL_SOCKET, SO_TYPE, &n, &len) == -1) {
|
||||
err = ngx_socket_errno;
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
|
||||
"invalid descriptor #%d in read fd_set", s);
|
||||
|
||||
FD_CLR(s, &master_read_fd_set);
|
||||
}
|
||||
}
|
||||
|
||||
for (s = 0; s <= max_fd; s++) {
|
||||
|
||||
if (FD_ISSET(s, &master_write_fd_set) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
len = sizeof(int);
|
||||
|
||||
if (getsockopt(s, SOL_SOCKET, SO_TYPE, &n, &len) == -1) {
|
||||
err = ngx_socket_errno;
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
|
||||
"invalid descriptor #%d in write fd_set", s);
|
||||
|
||||
FD_CLR(s, &master_write_fd_set);
|
||||
}
|
||||
}
|
||||
|
||||
max_fd = -1;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_select_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_event_conf_t *ecf;
|
||||
|
||||
ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
|
||||
|
||||
if (ecf->use != ngx_select_module.ctx_index) {
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
/* disable warning: the default FD_SETSIZE is 1024U in FreeBSD 5.x */
|
||||
|
||||
if (cycle->connection_n > FD_SETSIZE) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
|
||||
"the maximum number of files "
|
||||
"supported by select() is %ud", FD_SETSIZE);
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Maxim Dounin
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
static ngx_int_t ngx_poll_init(ngx_cycle_t *cycle, ngx_msec_t timer);
|
||||
static void ngx_poll_done(ngx_cycle_t *cycle);
|
||||
static ngx_int_t ngx_poll_add_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_poll_del_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_poll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags);
|
||||
static char *ngx_poll_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
|
||||
static struct pollfd *event_list;
|
||||
static ngx_connection_t **event_index;
|
||||
static ngx_uint_t nevents;
|
||||
|
||||
|
||||
static ngx_str_t poll_name = ngx_string("poll");
|
||||
|
||||
static ngx_event_module_t ngx_poll_module_ctx = {
|
||||
&poll_name,
|
||||
NULL, /* create configuration */
|
||||
ngx_poll_init_conf, /* init configuration */
|
||||
|
||||
{
|
||||
ngx_poll_add_event, /* add an event */
|
||||
ngx_poll_del_event, /* delete an event */
|
||||
ngx_poll_add_event, /* enable an event */
|
||||
ngx_poll_del_event, /* disable an event */
|
||||
NULL, /* add an connection */
|
||||
NULL, /* delete an connection */
|
||||
NULL, /* trigger a notify */
|
||||
ngx_poll_process_events, /* process the events */
|
||||
ngx_poll_init, /* init the events */
|
||||
ngx_poll_done /* done the events */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ngx_module_t ngx_poll_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_poll_module_ctx, /* module context */
|
||||
NULL, /* module directives */
|
||||
NGX_EVENT_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_poll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
|
||||
{
|
||||
struct pollfd *list;
|
||||
ngx_connection_t **index;
|
||||
|
||||
if (event_list == NULL) {
|
||||
nevents = 0;
|
||||
}
|
||||
|
||||
if (ngx_process >= NGX_PROCESS_WORKER
|
||||
|| cycle->old_cycle == NULL
|
||||
|| cycle->old_cycle->connection_n < cycle->connection_n)
|
||||
{
|
||||
list = ngx_alloc(sizeof(struct pollfd) * cycle->connection_n,
|
||||
cycle->log);
|
||||
if (list == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (event_list) {
|
||||
ngx_memcpy(list, event_list, sizeof(struct pollfd) * nevents);
|
||||
ngx_free(event_list);
|
||||
}
|
||||
|
||||
event_list = list;
|
||||
|
||||
index = ngx_alloc(sizeof(ngx_connection_t *) * cycle->connection_n,
|
||||
cycle->log);
|
||||
if (index == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (event_index) {
|
||||
ngx_memcpy(index, event_index,
|
||||
sizeof(ngx_connection_t *) * nevents);
|
||||
ngx_free(event_index);
|
||||
}
|
||||
|
||||
event_index = index;
|
||||
}
|
||||
|
||||
ngx_io = ngx_os_io;
|
||||
|
||||
ngx_event_actions = ngx_poll_module_ctx.actions;
|
||||
|
||||
ngx_event_flags = NGX_USE_LEVEL_EVENT;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_poll_done(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_free(event_list);
|
||||
ngx_free(event_index);
|
||||
|
||||
event_list = NULL;
|
||||
event_index = NULL;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_poll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ev->active = 1;
|
||||
|
||||
if (ev->index != NGX_INVALID_INDEX) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"poll event fd:%d ev:%i is already set", c->fd, event);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
e = c->write;
|
||||
#if (NGX_READ_EVENT != POLLIN)
|
||||
event = POLLIN;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
e = c->read;
|
||||
#if (NGX_WRITE_EVENT != POLLOUT)
|
||||
event = POLLOUT;
|
||||
#endif
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"poll add event: fd:%d ev:%i", c->fd, event);
|
||||
|
||||
if (e == NULL || e->index == NGX_INVALID_INDEX) {
|
||||
|
||||
event_list[nevents].fd = c->fd;
|
||||
event_list[nevents].events = (short) event;
|
||||
event_list[nevents].revents = 0;
|
||||
|
||||
event_index[nevents] = c;
|
||||
|
||||
ev->index = nevents;
|
||||
nevents++;
|
||||
|
||||
} else {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"poll add index: %i", e->index);
|
||||
|
||||
event_list[e->index].events |= (short) event;
|
||||
ev->index = e->index;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_poll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ev->active = 0;
|
||||
|
||||
if (ev->index == NGX_INVALID_INDEX) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"poll event fd:%d ev:%i is already deleted",
|
||||
c->fd, event);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
e = c->write;
|
||||
#if (NGX_READ_EVENT != POLLIN)
|
||||
event = POLLIN;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
e = c->read;
|
||||
#if (NGX_WRITE_EVENT != POLLOUT)
|
||||
event = POLLOUT;
|
||||
#endif
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"poll del event: fd:%d ev:%i", c->fd, event);
|
||||
|
||||
if (e == NULL || e->index == NGX_INVALID_INDEX) {
|
||||
nevents--;
|
||||
|
||||
if (ev->index < nevents) {
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"index: copy event %ui to %i", nevents, ev->index);
|
||||
|
||||
event_list[ev->index] = event_list[nevents];
|
||||
event_index[ev->index] = event_index[nevents];
|
||||
|
||||
c = event_index[ev->index];
|
||||
|
||||
if (c->fd == (ngx_socket_t) -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"unexpected last event");
|
||||
|
||||
} else {
|
||||
if (c->read->index == nevents) {
|
||||
c->read->index = ev->index;
|
||||
}
|
||||
|
||||
if (c->write->index == nevents) {
|
||||
c->write->index = ev->index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"poll del index: %i", e->index);
|
||||
|
||||
event_list[e->index].events &= (short) ~event;
|
||||
}
|
||||
|
||||
ev->index = NGX_INVALID_INDEX;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_poll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
|
||||
{
|
||||
int ready, revents;
|
||||
ngx_err_t err;
|
||||
ngx_uint_t i, found;
|
||||
ngx_event_t *ev;
|
||||
ngx_queue_t *queue;
|
||||
ngx_connection_t *c;
|
||||
|
||||
/* NGX_TIMER_INFINITE == INFTIM */
|
||||
|
||||
#if (NGX_DEBUG0)
|
||||
if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
|
||||
for (i = 0; i < nevents; i++) {
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"poll: %ui: fd:%d ev:%04Xd",
|
||||
i, event_list[i].fd, event_list[i].events);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "poll timer: %M", timer);
|
||||
|
||||
ready = WSAPoll(event_list, (u_int) nevents, (int) timer);
|
||||
|
||||
err = (ready == -1) ? ngx_errno : 0;
|
||||
|
||||
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
|
||||
ngx_time_update();
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"poll ready %d of %ui", ready, nevents);
|
||||
|
||||
if (err) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, err, "WSAPoll() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ready == 0) {
|
||||
if (timer != NGX_TIMER_INFINITE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"WSAPoll() returned no events without timeout");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; i < nevents && ready; i++) {
|
||||
|
||||
revents = event_list[i].revents;
|
||||
|
||||
#if 1
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"poll: %ui: fd:%d ev:%04Xd rev:%04Xd",
|
||||
i, event_list[i].fd, event_list[i].events, revents);
|
||||
#else
|
||||
if (revents) {
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"poll: %ui: fd:%d ev:%04Xd rev:%04Xd",
|
||||
i, event_list[i].fd, event_list[i].events, revents);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (revents & POLLNVAL) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"poll() error fd:%d ev:%04Xd rev:%04Xd",
|
||||
event_list[i].fd, event_list[i].events, revents);
|
||||
}
|
||||
|
||||
if (revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"strange poll() events fd:%d ev:%04Xd rev:%04Xd",
|
||||
event_list[i].fd, event_list[i].events, revents);
|
||||
}
|
||||
|
||||
if (event_list[i].fd == (ngx_socket_t) -1) {
|
||||
/*
|
||||
* the disabled event, a workaround for our possible bug,
|
||||
* see the comment below
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
c = event_index[i];
|
||||
|
||||
if (c->fd == (ngx_socket_t) -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "unexpected event");
|
||||
|
||||
/*
|
||||
* it is certainly our fault and it should be investigated,
|
||||
* in the meantime we disable this event to avoid a CPU spinning
|
||||
*/
|
||||
|
||||
if (i == nevents - 1) {
|
||||
nevents--;
|
||||
} else {
|
||||
event_list[i].fd = (ngx_socket_t) -1;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
|
||||
|
||||
/*
|
||||
* if the error events were returned, add POLLIN and POLLOUT
|
||||
* to handle the events at least in one active handler
|
||||
*/
|
||||
|
||||
revents |= POLLIN|POLLOUT;
|
||||
}
|
||||
|
||||
found = 0;
|
||||
|
||||
if ((revents & POLLIN) && c->read->active) {
|
||||
found = 1;
|
||||
|
||||
ev = c->read;
|
||||
ev->ready = 1;
|
||||
ev->available = -1;
|
||||
|
||||
queue = ev->accept ? &ngx_posted_accept_events
|
||||
: &ngx_posted_events;
|
||||
|
||||
ngx_post_event(ev, queue);
|
||||
}
|
||||
|
||||
if ((revents & POLLOUT) && c->write->active) {
|
||||
found = 1;
|
||||
|
||||
ev = c->write;
|
||||
ev->ready = 1;
|
||||
|
||||
ngx_post_event(ev, &ngx_posted_events);
|
||||
}
|
||||
|
||||
if (found) {
|
||||
ready--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ready != 0) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "poll ready != events");
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_poll_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_event_conf_t *ecf;
|
||||
|
||||
ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
|
||||
|
||||
if (ecf->use != ngx_poll_module.ctx_index) {
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
#if (NGX_LOAD_WSAPOLL)
|
||||
|
||||
if (!ngx_have_wsapoll) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
|
||||
"poll is not available on this platform");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
static ngx_int_t ngx_select_init(ngx_cycle_t *cycle, ngx_msec_t timer);
|
||||
static void ngx_select_done(ngx_cycle_t *cycle);
|
||||
static ngx_int_t ngx_select_add_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_select_del_event(ngx_event_t *ev, ngx_int_t event,
|
||||
ngx_uint_t flags);
|
||||
static ngx_int_t ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags);
|
||||
static void ngx_select_repair_fd_sets(ngx_cycle_t *cycle);
|
||||
static char *ngx_select_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
|
||||
static fd_set master_read_fd_set;
|
||||
static fd_set master_write_fd_set;
|
||||
static fd_set work_read_fd_set;
|
||||
static fd_set work_write_fd_set;
|
||||
static fd_set work_except_fd_set;
|
||||
|
||||
static ngx_uint_t max_read;
|
||||
static ngx_uint_t max_write;
|
||||
static ngx_uint_t nevents;
|
||||
|
||||
static ngx_event_t **event_index;
|
||||
|
||||
|
||||
static ngx_str_t select_name = ngx_string("select");
|
||||
|
||||
static ngx_event_module_t ngx_select_module_ctx = {
|
||||
&select_name,
|
||||
NULL, /* create configuration */
|
||||
ngx_select_init_conf, /* init configuration */
|
||||
|
||||
{
|
||||
ngx_select_add_event, /* add an event */
|
||||
ngx_select_del_event, /* delete an event */
|
||||
ngx_select_add_event, /* enable an event */
|
||||
ngx_select_del_event, /* disable an event */
|
||||
NULL, /* add an connection */
|
||||
NULL, /* delete an connection */
|
||||
NULL, /* trigger a notify */
|
||||
ngx_select_process_events, /* process the events */
|
||||
ngx_select_init, /* init the events */
|
||||
ngx_select_done /* done the events */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ngx_module_t ngx_select_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_select_module_ctx, /* module context */
|
||||
NULL, /* module directives */
|
||||
NGX_EVENT_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_select_init(ngx_cycle_t *cycle, ngx_msec_t timer)
|
||||
{
|
||||
ngx_event_t **index;
|
||||
|
||||
if (event_index == NULL) {
|
||||
FD_ZERO(&master_read_fd_set);
|
||||
FD_ZERO(&master_write_fd_set);
|
||||
nevents = 0;
|
||||
}
|
||||
|
||||
if (ngx_process >= NGX_PROCESS_WORKER
|
||||
|| cycle->old_cycle == NULL
|
||||
|| cycle->old_cycle->connection_n < cycle->connection_n)
|
||||
{
|
||||
index = ngx_alloc(sizeof(ngx_event_t *) * 2 * cycle->connection_n,
|
||||
cycle->log);
|
||||
if (index == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (event_index) {
|
||||
ngx_memcpy(index, event_index, sizeof(ngx_event_t *) * nevents);
|
||||
ngx_free(event_index);
|
||||
}
|
||||
|
||||
event_index = index;
|
||||
}
|
||||
|
||||
ngx_io = ngx_os_io;
|
||||
|
||||
ngx_event_actions = ngx_select_module_ctx.actions;
|
||||
|
||||
ngx_event_flags = NGX_USE_LEVEL_EVENT;
|
||||
|
||||
max_read = 0;
|
||||
max_write = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_select_done(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_free(event_index);
|
||||
|
||||
event_index = NULL;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_select_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"select add event fd:%d ev:%i", c->fd, event);
|
||||
|
||||
if (ev->index != NGX_INVALID_INDEX) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"select event fd:%d ev:%i is already set", c->fd, event);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if ((event == NGX_READ_EVENT && ev->write)
|
||||
|| (event == NGX_WRITE_EVENT && !ev->write))
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"invalid select %s event fd:%d ev:%i",
|
||||
ev->write ? "write" : "read", c->fd, event);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if ((event == NGX_READ_EVENT && max_read >= FD_SETSIZE)
|
||||
|| (event == NGX_WRITE_EVENT && max_write >= FD_SETSIZE))
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, ev->log, 0,
|
||||
"maximum number of descriptors "
|
||||
"supported by select() is %d", FD_SETSIZE);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
FD_SET(c->fd, &master_read_fd_set);
|
||||
max_read++;
|
||||
|
||||
} else if (event == NGX_WRITE_EVENT) {
|
||||
FD_SET(c->fd, &master_write_fd_set);
|
||||
max_write++;
|
||||
}
|
||||
|
||||
ev->active = 1;
|
||||
|
||||
event_index[nevents] = ev;
|
||||
ev->index = nevents;
|
||||
nevents++;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_select_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
|
||||
{
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ev->active = 0;
|
||||
|
||||
if (ev->index == NGX_INVALID_INDEX) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"select del event fd:%d ev:%i", c->fd, event);
|
||||
|
||||
if (event == NGX_READ_EVENT) {
|
||||
FD_CLR(c->fd, &master_read_fd_set);
|
||||
max_read--;
|
||||
|
||||
} else if (event == NGX_WRITE_EVENT) {
|
||||
FD_CLR(c->fd, &master_write_fd_set);
|
||||
max_write--;
|
||||
}
|
||||
|
||||
if (ev->index < --nevents) {
|
||||
e = event_index[nevents];
|
||||
event_index[ev->index] = e;
|
||||
e->index = ev->index;
|
||||
}
|
||||
|
||||
ev->index = NGX_INVALID_INDEX;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags)
|
||||
{
|
||||
int ready, nready;
|
||||
ngx_err_t err;
|
||||
ngx_uint_t i, found;
|
||||
ngx_event_t *ev;
|
||||
ngx_queue_t *queue;
|
||||
struct timeval tv, *tp;
|
||||
ngx_connection_t *c;
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
|
||||
for (i = 0; i < nevents; i++) {
|
||||
ev = event_index[i];
|
||||
c = ev->data;
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select event: fd:%d wr:%d", c->fd, ev->write);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (timer == NGX_TIMER_INFINITE) {
|
||||
tp = NULL;
|
||||
|
||||
} else {
|
||||
tv.tv_sec = (long) (timer / 1000);
|
||||
tv.tv_usec = (long) ((timer % 1000) * 1000);
|
||||
tp = &tv;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select timer: %M", timer);
|
||||
|
||||
work_read_fd_set = master_read_fd_set;
|
||||
work_write_fd_set = master_write_fd_set;
|
||||
work_except_fd_set = master_write_fd_set;
|
||||
|
||||
if (max_read || max_write) {
|
||||
ready = select(0, &work_read_fd_set, &work_write_fd_set,
|
||||
&work_except_fd_set, tp);
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
* Winsock select() requires that at least one descriptor set must be
|
||||
* be non-null, and any non-null descriptor set must contain at least
|
||||
* one handle to a socket. Otherwise select() returns WSAEINVAL.
|
||||
*/
|
||||
|
||||
ngx_msleep(timer);
|
||||
|
||||
ready = 0;
|
||||
}
|
||||
|
||||
err = (ready == -1) ? ngx_socket_errno : 0;
|
||||
|
||||
if (flags & NGX_UPDATE_TIME) {
|
||||
ngx_time_update();
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select ready %d", ready);
|
||||
|
||||
if (err) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, err, "select() failed");
|
||||
|
||||
if (err == WSAENOTSOCK) {
|
||||
ngx_select_repair_fd_sets(cycle);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ready == 0) {
|
||||
if (timer != NGX_TIMER_INFINITE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"select() returned no events without timeout");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
nready = 0;
|
||||
|
||||
for (i = 0; i < nevents; i++) {
|
||||
ev = event_index[i];
|
||||
c = ev->data;
|
||||
found = 0;
|
||||
|
||||
if (ev->write) {
|
||||
if (FD_ISSET(c->fd, &work_write_fd_set)) {
|
||||
found++;
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select write %d", c->fd);
|
||||
}
|
||||
|
||||
if (FD_ISSET(c->fd, &work_except_fd_set)) {
|
||||
found++;
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select except %d", c->fd);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (FD_ISSET(c->fd, &work_read_fd_set)) {
|
||||
found++;
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"select read %d", c->fd);
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
ev->ready = 1;
|
||||
ev->available = -1;
|
||||
|
||||
queue = ev->accept ? &ngx_posted_accept_events
|
||||
: &ngx_posted_events;
|
||||
|
||||
ngx_post_event(ev, queue);
|
||||
|
||||
nready += found;
|
||||
}
|
||||
}
|
||||
|
||||
if (ready != nready) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
|
||||
"select ready != events: %d:%d", ready, nready);
|
||||
|
||||
ngx_select_repair_fd_sets(cycle);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_select_repair_fd_sets(ngx_cycle_t *cycle)
|
||||
{
|
||||
int n;
|
||||
u_int i;
|
||||
socklen_t len;
|
||||
ngx_err_t err;
|
||||
ngx_socket_t s;
|
||||
|
||||
for (i = 0; i < master_read_fd_set.fd_count; i++) {
|
||||
|
||||
s = master_read_fd_set.fd_array[i];
|
||||
len = sizeof(int);
|
||||
|
||||
if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &n, &len) == -1) {
|
||||
err = ngx_socket_errno;
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
|
||||
"invalid descriptor #%d in read fd_set", s);
|
||||
|
||||
FD_CLR(s, &master_read_fd_set);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < master_write_fd_set.fd_count; i++) {
|
||||
|
||||
s = master_write_fd_set.fd_array[i];
|
||||
len = sizeof(int);
|
||||
|
||||
if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &n, &len) == -1) {
|
||||
err = ngx_socket_errno;
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
|
||||
"invalid descriptor #%d in write fd_set", s);
|
||||
|
||||
FD_CLR(s, &master_write_fd_set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_select_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_event_conf_t *ecf;
|
||||
|
||||
ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
|
||||
|
||||
if (ecf->use != ngx_select_module.ctx_index) {
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,1342 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
#define DEFAULT_CONNECTIONS 512
|
||||
|
||||
|
||||
extern ngx_module_t ngx_kqueue_module;
|
||||
extern ngx_module_t ngx_eventport_module;
|
||||
extern ngx_module_t ngx_devpoll_module;
|
||||
extern ngx_module_t ngx_epoll_module;
|
||||
extern ngx_module_t ngx_select_module;
|
||||
|
||||
|
||||
static char *ngx_event_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
static ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle);
|
||||
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle);
|
||||
static char *ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
|
||||
|
||||
static char *ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||
void *conf);
|
||||
static char *ngx_event_use(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
|
||||
static char *ngx_event_debug_connection(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||
void *conf);
|
||||
|
||||
static void *ngx_event_core_create_conf(ngx_cycle_t *cycle);
|
||||
static char *ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
|
||||
static ngx_uint_t ngx_timer_resolution;
|
||||
sig_atomic_t ngx_event_timer_alarm;
|
||||
|
||||
static ngx_uint_t ngx_event_max_module;
|
||||
|
||||
ngx_uint_t ngx_event_flags;
|
||||
ngx_event_actions_t ngx_event_actions;
|
||||
|
||||
|
||||
static ngx_atomic_t connection_counter = 1;
|
||||
ngx_atomic_t *ngx_connection_counter = &connection_counter;
|
||||
|
||||
|
||||
ngx_atomic_t *ngx_accept_mutex_ptr;
|
||||
ngx_shmtx_t ngx_accept_mutex;
|
||||
ngx_uint_t ngx_use_accept_mutex;
|
||||
ngx_uint_t ngx_accept_events;
|
||||
ngx_uint_t ngx_accept_mutex_held;
|
||||
ngx_msec_t ngx_accept_mutex_delay;
|
||||
ngx_int_t ngx_accept_disabled;
|
||||
ngx_uint_t ngx_use_exclusive_accept;
|
||||
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
|
||||
static ngx_atomic_t ngx_stat_accepted0;
|
||||
ngx_atomic_t *ngx_stat_accepted = &ngx_stat_accepted0;
|
||||
static ngx_atomic_t ngx_stat_handled0;
|
||||
ngx_atomic_t *ngx_stat_handled = &ngx_stat_handled0;
|
||||
static ngx_atomic_t ngx_stat_requests0;
|
||||
ngx_atomic_t *ngx_stat_requests = &ngx_stat_requests0;
|
||||
static ngx_atomic_t ngx_stat_active0;
|
||||
ngx_atomic_t *ngx_stat_active = &ngx_stat_active0;
|
||||
static ngx_atomic_t ngx_stat_reading0;
|
||||
ngx_atomic_t *ngx_stat_reading = &ngx_stat_reading0;
|
||||
static ngx_atomic_t ngx_stat_writing0;
|
||||
ngx_atomic_t *ngx_stat_writing = &ngx_stat_writing0;
|
||||
static ngx_atomic_t ngx_stat_waiting0;
|
||||
ngx_atomic_t *ngx_stat_waiting = &ngx_stat_waiting0;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
static ngx_command_t ngx_events_commands[] = {
|
||||
|
||||
{ ngx_string("events"),
|
||||
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
|
||||
ngx_events_block,
|
||||
0,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_core_module_t ngx_events_module_ctx = {
|
||||
ngx_string("events"),
|
||||
NULL,
|
||||
ngx_event_init_conf
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_events_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_events_module_ctx, /* module context */
|
||||
ngx_events_commands, /* module directives */
|
||||
NGX_CORE_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_str_t event_core_name = ngx_string("event_core");
|
||||
|
||||
|
||||
static ngx_command_t ngx_event_core_commands[] = {
|
||||
|
||||
{ ngx_string("worker_connections"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_event_connections,
|
||||
0,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
{ ngx_string("use"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_event_use,
|
||||
0,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
{ ngx_string("multi_accept"),
|
||||
NGX_EVENT_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
0,
|
||||
offsetof(ngx_event_conf_t, multi_accept),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("accept_mutex"),
|
||||
NGX_EVENT_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
0,
|
||||
offsetof(ngx_event_conf_t, accept_mutex),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("accept_mutex_delay"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
0,
|
||||
offsetof(ngx_event_conf_t, accept_mutex_delay),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("debug_connection"),
|
||||
NGX_EVENT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_event_debug_connection,
|
||||
0,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_event_module_t ngx_event_core_module_ctx = {
|
||||
&event_core_name,
|
||||
ngx_event_core_create_conf, /* create configuration */
|
||||
ngx_event_core_init_conf, /* init configuration */
|
||||
|
||||
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_event_core_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_event_core_module_ctx, /* module context */
|
||||
ngx_event_core_commands, /* module directives */
|
||||
NGX_EVENT_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
ngx_event_module_init, /* init module */
|
||||
ngx_event_process_init, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
void
|
||||
ngx_process_events_and_timers(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_uint_t flags;
|
||||
ngx_msec_t timer, delta;
|
||||
|
||||
if (ngx_timer_resolution) {
|
||||
timer = NGX_TIMER_INFINITE;
|
||||
flags = 0;
|
||||
|
||||
} else {
|
||||
timer = ngx_event_find_timer();
|
||||
flags = NGX_UPDATE_TIME;
|
||||
|
||||
#if (NGX_WIN32)
|
||||
|
||||
/* handle signals from master in case of network inactivity */
|
||||
|
||||
if (timer == NGX_TIMER_INFINITE || timer > 500) {
|
||||
timer = 500;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ngx_use_accept_mutex) {
|
||||
if (ngx_accept_disabled > 0) {
|
||||
ngx_accept_disabled--;
|
||||
|
||||
} else {
|
||||
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ngx_accept_mutex_held) {
|
||||
flags |= NGX_POST_EVENTS;
|
||||
|
||||
} else {
|
||||
if (timer == NGX_TIMER_INFINITE
|
||||
|| timer > ngx_accept_mutex_delay)
|
||||
{
|
||||
timer = ngx_accept_mutex_delay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ngx_queue_empty(&ngx_posted_next_events)) {
|
||||
ngx_event_move_posted_next(cycle);
|
||||
timer = 0;
|
||||
}
|
||||
|
||||
delta = ngx_current_msec;
|
||||
|
||||
(void) ngx_process_events(cycle, timer, flags);
|
||||
|
||||
delta = ngx_current_msec - delta;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"timer delta: %M", delta);
|
||||
|
||||
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
|
||||
|
||||
if (ngx_accept_mutex_held) {
|
||||
ngx_shmtx_unlock(&ngx_accept_mutex);
|
||||
}
|
||||
|
||||
ngx_event_expire_timers();
|
||||
|
||||
ngx_event_process_posted(cycle, &ngx_posted_events);
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
|
||||
{
|
||||
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
|
||||
|
||||
/* kqueue, epoll */
|
||||
|
||||
if (!rev->active && !rev->ready) {
|
||||
if (ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
} else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
|
||||
|
||||
/* select, poll, /dev/poll */
|
||||
|
||||
if (!rev->active && !rev->ready) {
|
||||
if (ngx_add_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) {
|
||||
if (ngx_del_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT | flags)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
} else if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
|
||||
|
||||
/* event ports */
|
||||
|
||||
if (!rev->active && !rev->ready) {
|
||||
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (rev->oneshot && rev->ready) {
|
||||
if (ngx_del_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* iocp */
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_handle_write_event(ngx_event_t *wev, size_t lowat)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
if (lowat) {
|
||||
c = wev->data;
|
||||
|
||||
if (ngx_send_lowat(c, lowat) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
|
||||
|
||||
/* kqueue, epoll */
|
||||
|
||||
if (!wev->active && !wev->ready) {
|
||||
if (ngx_add_event(wev, NGX_WRITE_EVENT,
|
||||
NGX_CLEAR_EVENT | (lowat ? NGX_LOWAT_EVENT : 0))
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
} else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
|
||||
|
||||
/* select, poll, /dev/poll */
|
||||
|
||||
if (!wev->active && !wev->ready) {
|
||||
if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (wev->active && wev->ready) {
|
||||
if (ngx_del_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
} else if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
|
||||
|
||||
/* event ports */
|
||||
|
||||
if (!wev->active && !wev->ready) {
|
||||
if (ngx_add_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (wev->oneshot && wev->ready) {
|
||||
if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* iocp */
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_event_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
#if (NGX_HAVE_REUSEPORT)
|
||||
ngx_uint_t i;
|
||||
ngx_listening_t *ls;
|
||||
#endif
|
||||
|
||||
if (ngx_get_conf(cycle->conf_ctx, ngx_events_module) == NULL) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
|
||||
"no \"events\" section in configuration");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
if (cycle->connection_n < cycle->listening.nelts + 1) {
|
||||
|
||||
/*
|
||||
* there should be at least one connection for each listening
|
||||
* socket, plus an additional connection for channel
|
||||
*/
|
||||
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
|
||||
"%ui worker_connections are not enough "
|
||||
"for %ui listening sockets",
|
||||
cycle->connection_n, cycle->listening.nelts);
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_REUSEPORT)
|
||||
|
||||
if (!ngx_test_config) {
|
||||
|
||||
ls = cycle->listening.elts;
|
||||
for (i = 0; i < cycle->listening.nelts; i++) {
|
||||
|
||||
if (!ls[i].reuseport || ls[i].worker != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ngx_clone_listening(cycle, &ls[i]) != NGX_OK) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* cloning may change cycle->listening.elts */
|
||||
|
||||
ls = cycle->listening.elts;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_event_module_init(ngx_cycle_t *cycle)
|
||||
{
|
||||
void ***cf;
|
||||
u_char *shared;
|
||||
size_t size, cl;
|
||||
ngx_shm_t shm;
|
||||
ngx_time_t *tp;
|
||||
ngx_core_conf_t *ccf;
|
||||
ngx_event_conf_t *ecf;
|
||||
|
||||
cf = ngx_get_conf(cycle->conf_ctx, ngx_events_module);
|
||||
ecf = (*cf)[ngx_event_core_module.ctx_index];
|
||||
|
||||
if (!ngx_test_config && ngx_process <= NGX_PROCESS_MASTER) {
|
||||
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
|
||||
"using the \"%s\" event method", ecf->name);
|
||||
}
|
||||
|
||||
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
|
||||
|
||||
ngx_timer_resolution = ccf->timer_resolution;
|
||||
|
||||
#if !(NGX_WIN32)
|
||||
{
|
||||
ngx_int_t limit;
|
||||
struct rlimit rlmt;
|
||||
|
||||
if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"getrlimit(RLIMIT_NOFILE) failed, ignored");
|
||||
|
||||
} else {
|
||||
if (ecf->connections > (ngx_uint_t) rlmt.rlim_cur
|
||||
&& (ccf->rlimit_nofile == NGX_CONF_UNSET
|
||||
|| ecf->connections > (ngx_uint_t) ccf->rlimit_nofile))
|
||||
{
|
||||
limit = (ccf->rlimit_nofile == NGX_CONF_UNSET) ?
|
||||
(ngx_int_t) rlmt.rlim_cur : ccf->rlimit_nofile;
|
||||
|
||||
ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
|
||||
"%ui worker_connections exceed "
|
||||
"open file resource limit: %i",
|
||||
ecf->connections, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* !(NGX_WIN32) */
|
||||
|
||||
|
||||
if (ccf->master == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_accept_mutex_ptr) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
/* cl should be equal to or greater than cache line size */
|
||||
|
||||
cl = 128;
|
||||
|
||||
size = cl /* ngx_accept_mutex */
|
||||
+ cl /* ngx_connection_counter */
|
||||
+ cl; /* ngx_temp_number */
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
|
||||
size += cl /* ngx_stat_accepted */
|
||||
+ cl /* ngx_stat_handled */
|
||||
+ cl /* ngx_stat_requests */
|
||||
+ cl /* ngx_stat_active */
|
||||
+ cl /* ngx_stat_reading */
|
||||
+ cl /* ngx_stat_writing */
|
||||
+ cl; /* ngx_stat_waiting */
|
||||
|
||||
#endif
|
||||
|
||||
shm.size = size;
|
||||
ngx_str_set(&shm.name, "nginx_shared_zone");
|
||||
shm.log = cycle->log;
|
||||
|
||||
if (ngx_shm_alloc(&shm) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
shared = shm.addr;
|
||||
|
||||
ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
|
||||
ngx_accept_mutex.spin = (ngx_uint_t) -1;
|
||||
|
||||
if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,
|
||||
cycle->lock_file.data)
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);
|
||||
|
||||
(void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"counter: %p, %uA",
|
||||
ngx_connection_counter, *ngx_connection_counter);
|
||||
|
||||
ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl);
|
||||
|
||||
tp = ngx_timeofday();
|
||||
|
||||
ngx_random_number = (tp->msec << 16) + ngx_pid;
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
|
||||
ngx_stat_accepted = (ngx_atomic_t *) (shared + 3 * cl);
|
||||
ngx_stat_handled = (ngx_atomic_t *) (shared + 4 * cl);
|
||||
ngx_stat_requests = (ngx_atomic_t *) (shared + 5 * cl);
|
||||
ngx_stat_active = (ngx_atomic_t *) (shared + 6 * cl);
|
||||
ngx_stat_reading = (ngx_atomic_t *) (shared + 7 * cl);
|
||||
ngx_stat_writing = (ngx_atomic_t *) (shared + 8 * cl);
|
||||
ngx_stat_waiting = (ngx_atomic_t *) (shared + 9 * cl);
|
||||
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#if !(NGX_WIN32)
|
||||
|
||||
static void
|
||||
ngx_timer_signal_handler(int signo)
|
||||
{
|
||||
ngx_event_timer_alarm = 1;
|
||||
|
||||
#if 1
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer signal");
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_event_process_init(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_uint_t m, i;
|
||||
ngx_event_t *rev, *wev;
|
||||
ngx_listening_t *ls;
|
||||
ngx_connection_t *c, *next, *old;
|
||||
ngx_core_conf_t *ccf;
|
||||
ngx_event_conf_t *ecf;
|
||||
ngx_event_module_t *module;
|
||||
|
||||
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
|
||||
ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
|
||||
|
||||
if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
|
||||
ngx_use_accept_mutex = 1;
|
||||
ngx_accept_mutex_held = 0;
|
||||
ngx_accept_mutex_delay = ecf->accept_mutex_delay;
|
||||
|
||||
} else {
|
||||
ngx_use_accept_mutex = 0;
|
||||
}
|
||||
|
||||
#if (NGX_WIN32)
|
||||
|
||||
/*
|
||||
* disable accept mutex on win32 as it may cause deadlock if
|
||||
* grabbed by a process which can't accept connections
|
||||
*/
|
||||
|
||||
ngx_use_accept_mutex = 0;
|
||||
|
||||
#endif
|
||||
|
||||
ngx_use_exclusive_accept = 0;
|
||||
|
||||
ngx_queue_init(&ngx_posted_accept_events);
|
||||
ngx_queue_init(&ngx_posted_next_events);
|
||||
ngx_queue_init(&ngx_posted_events);
|
||||
|
||||
if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
for (m = 0; cycle->modules[m]; m++) {
|
||||
if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cycle->modules[m]->ctx_index != ecf->use) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = cycle->modules[m]->ctx;
|
||||
|
||||
if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
|
||||
/* fatal */
|
||||
exit(2);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
#if !(NGX_WIN32)
|
||||
|
||||
if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
|
||||
struct sigaction sa;
|
||||
struct itimerval itv;
|
||||
|
||||
ngx_memzero(&sa, sizeof(struct sigaction));
|
||||
sa.sa_handler = ngx_timer_signal_handler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
if (sigaction(SIGALRM, &sa, NULL) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"sigaction(SIGALRM) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
|
||||
itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
|
||||
itv.it_value.tv_sec = ngx_timer_resolution / 1000;
|
||||
itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
|
||||
|
||||
if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"setitimer() failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (ngx_event_flags & NGX_USE_FD_EVENT) {
|
||||
struct rlimit rlmt;
|
||||
|
||||
if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
|
||||
"getrlimit(RLIMIT_NOFILE) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cycle->files_n = (ngx_uint_t) rlmt.rlim_cur;
|
||||
|
||||
cycle->files = ngx_calloc(sizeof(ngx_connection_t *) * cycle->files_n,
|
||||
cycle->log);
|
||||
if (cycle->files == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
|
||||
ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
|
||||
"the \"timer_resolution\" directive is not supported "
|
||||
"with the configured event method, ignored");
|
||||
ngx_timer_resolution = 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
cycle->connections =
|
||||
ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
|
||||
if (cycle->connections == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c = cycle->connections;
|
||||
|
||||
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
|
||||
cycle->log);
|
||||
if (cycle->read_events == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
rev = cycle->read_events;
|
||||
for (i = 0; i < cycle->connection_n; i++) {
|
||||
rev[i].closed = 1;
|
||||
rev[i].instance = 1;
|
||||
}
|
||||
|
||||
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
|
||||
cycle->log);
|
||||
if (cycle->write_events == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
wev = cycle->write_events;
|
||||
for (i = 0; i < cycle->connection_n; i++) {
|
||||
wev[i].closed = 1;
|
||||
}
|
||||
|
||||
i = cycle->connection_n;
|
||||
next = NULL;
|
||||
|
||||
do {
|
||||
i--;
|
||||
|
||||
c[i].data = next;
|
||||
c[i].read = &cycle->read_events[i];
|
||||
c[i].write = &cycle->write_events[i];
|
||||
c[i].fd = (ngx_socket_t) -1;
|
||||
|
||||
next = &c[i];
|
||||
} while (i);
|
||||
|
||||
cycle->free_connections = next;
|
||||
cycle->free_connection_n = cycle->connection_n;
|
||||
|
||||
/* for each listening socket */
|
||||
|
||||
ls = cycle->listening.elts;
|
||||
for (i = 0; i < cycle->listening.nelts; i++) {
|
||||
|
||||
#if (NGX_HAVE_REUSEPORT)
|
||||
if (ls[i].reuseport && ls[i].worker != ngx_worker) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
c = ngx_get_connection(ls[i].fd, cycle->log);
|
||||
|
||||
if (c == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->type = ls[i].type;
|
||||
c->log = &ls[i].log;
|
||||
|
||||
c->listening = &ls[i];
|
||||
ls[i].connection = c;
|
||||
|
||||
rev = c->read;
|
||||
|
||||
rev->log = c->log;
|
||||
rev->accept = 1;
|
||||
|
||||
#if (NGX_HAVE_DEFERRED_ACCEPT)
|
||||
rev->deferred_accept = ls[i].deferred_accept;
|
||||
#endif
|
||||
|
||||
if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
|
||||
if (ls[i].previous) {
|
||||
|
||||
/*
|
||||
* delete the old accept events that were bound to
|
||||
* the old cycle read events array
|
||||
*/
|
||||
|
||||
old = ls[i].previous->connection;
|
||||
|
||||
if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
old->fd = (ngx_socket_t) -1;
|
||||
}
|
||||
}
|
||||
|
||||
#if (NGX_WIN32)
|
||||
|
||||
if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
|
||||
ngx_iocp_conf_t *iocpcf;
|
||||
|
||||
rev->handler = ngx_event_acceptex;
|
||||
|
||||
if (ngx_use_accept_mutex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ngx_add_event(rev, 0, NGX_IOCP_ACCEPT) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ls[i].log.handler = ngx_acceptex_log_error;
|
||||
|
||||
iocpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module);
|
||||
if (ngx_event_post_acceptex(&ls[i], iocpcf->post_acceptex)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
} else {
|
||||
rev->handler = ngx_event_accept;
|
||||
|
||||
if (ngx_use_accept_mutex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
|
||||
: ngx_event_recvmsg;
|
||||
|
||||
#if (NGX_HAVE_REUSEPORT)
|
||||
|
||||
if (ls[i].reuseport) {
|
||||
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (ngx_use_accept_mutex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_EPOLLEXCLUSIVE)
|
||||
|
||||
if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
|
||||
&& ccf->worker_processes > 1)
|
||||
{
|
||||
ngx_use_exclusive_accept = 1;
|
||||
|
||||
if (ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_send_lowat(ngx_connection_t *c, size_t lowat)
|
||||
{
|
||||
int sndlowat;
|
||||
|
||||
#if (NGX_HAVE_LOWAT_EVENT)
|
||||
|
||||
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
|
||||
c->write->available = lowat;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (lowat == 0 || c->sndlowat) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
sndlowat = (int) lowat;
|
||||
|
||||
if (setsockopt(c->fd, SOL_SOCKET, SO_SNDLOWAT,
|
||||
(const void *) &sndlowat, sizeof(int))
|
||||
== -1)
|
||||
{
|
||||
ngx_connection_error(c, ngx_socket_errno,
|
||||
"setsockopt(SO_SNDLOWAT) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->sndlowat = 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
char *rv;
|
||||
void ***ctx;
|
||||
ngx_uint_t i;
|
||||
ngx_conf_t pcf;
|
||||
ngx_event_module_t *m;
|
||||
|
||||
if (*(void **) conf) {
|
||||
return "is duplicate";
|
||||
}
|
||||
|
||||
/* count the number of the event modules and set up their indices */
|
||||
|
||||
ngx_event_max_module = ngx_count_modules(cf->cycle, NGX_EVENT_MODULE);
|
||||
|
||||
ctx = ngx_pcalloc(cf->pool, sizeof(void *));
|
||||
if (ctx == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
*ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
|
||||
if (*ctx == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
*(void **) conf = ctx;
|
||||
|
||||
for (i = 0; cf->cycle->modules[i]; i++) {
|
||||
if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m = cf->cycle->modules[i]->ctx;
|
||||
|
||||
if (m->create_conf) {
|
||||
(*ctx)[cf->cycle->modules[i]->ctx_index] =
|
||||
m->create_conf(cf->cycle);
|
||||
if ((*ctx)[cf->cycle->modules[i]->ctx_index] == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pcf = *cf;
|
||||
cf->ctx = ctx;
|
||||
cf->module_type = NGX_EVENT_MODULE;
|
||||
cf->cmd_type = NGX_EVENT_CONF;
|
||||
|
||||
rv = ngx_conf_parse(cf, NULL);
|
||||
|
||||
*cf = pcf;
|
||||
|
||||
if (rv != NGX_CONF_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
for (i = 0; cf->cycle->modules[i]; i++) {
|
||||
if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m = cf->cycle->modules[i]->ctx;
|
||||
|
||||
if (m->init_conf) {
|
||||
rv = m->init_conf(cf->cycle,
|
||||
(*ctx)[cf->cycle->modules[i]->ctx_index]);
|
||||
if (rv != NGX_CONF_OK) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
ngx_event_conf_t *ecf = conf;
|
||||
|
||||
ngx_str_t *value;
|
||||
|
||||
if (ecf->connections != NGX_CONF_UNSET_UINT) {
|
||||
return "is duplicate";
|
||||
}
|
||||
|
||||
value = cf->args->elts;
|
||||
ecf->connections = ngx_atoi(value[1].data, value[1].len);
|
||||
if (ecf->connections == (ngx_uint_t) NGX_ERROR) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"invalid number \"%V\"", &value[1]);
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
cf->cycle->connection_n = ecf->connections;
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_event_use(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
ngx_event_conf_t *ecf = conf;
|
||||
|
||||
ngx_int_t m;
|
||||
ngx_str_t *value;
|
||||
ngx_event_conf_t *old_ecf;
|
||||
ngx_event_module_t *module;
|
||||
|
||||
if (ecf->use != NGX_CONF_UNSET_UINT) {
|
||||
return "is duplicate";
|
||||
}
|
||||
|
||||
value = cf->args->elts;
|
||||
|
||||
if (cf->cycle->old_cycle->conf_ctx) {
|
||||
old_ecf = ngx_event_get_conf(cf->cycle->old_cycle->conf_ctx,
|
||||
ngx_event_core_module);
|
||||
} else {
|
||||
old_ecf = NULL;
|
||||
}
|
||||
|
||||
|
||||
for (m = 0; cf->cycle->modules[m]; m++) {
|
||||
if (cf->cycle->modules[m]->type != NGX_EVENT_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = cf->cycle->modules[m]->ctx;
|
||||
if (module->name->len == value[1].len) {
|
||||
if (ngx_strcmp(module->name->data, value[1].data) == 0) {
|
||||
ecf->use = cf->cycle->modules[m]->ctx_index;
|
||||
ecf->name = module->name->data;
|
||||
|
||||
if (ngx_process == NGX_PROCESS_SINGLE
|
||||
&& old_ecf
|
||||
&& old_ecf->use != ecf->use)
|
||||
{
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"when the server runs without a master process "
|
||||
"the \"%V\" event type must be the same as "
|
||||
"in previous configuration - \"%s\" "
|
||||
"and it cannot be changed on the fly, "
|
||||
"to change it you need to stop server "
|
||||
"and start it again",
|
||||
&value[1], old_ecf->name);
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"invalid event type \"%V\"", &value[1]);
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_event_debug_connection(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
#if (NGX_DEBUG)
|
||||
ngx_event_conf_t *ecf = conf;
|
||||
|
||||
ngx_int_t rc;
|
||||
ngx_str_t *value;
|
||||
ngx_url_t u;
|
||||
ngx_cidr_t c, *cidr;
|
||||
ngx_uint_t i;
|
||||
struct sockaddr_in *sin;
|
||||
#if (NGX_HAVE_INET6)
|
||||
struct sockaddr_in6 *sin6;
|
||||
#endif
|
||||
|
||||
value = cf->args->elts;
|
||||
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
|
||||
if (ngx_strcmp(value[1].data, "unix:") == 0) {
|
||||
cidr = ngx_array_push(&ecf->debug_connection);
|
||||
if (cidr == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
cidr->family = AF_UNIX;
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
rc = ngx_ptocidr(&value[1], &c);
|
||||
|
||||
if (rc != NGX_ERROR) {
|
||||
if (rc == NGX_DONE) {
|
||||
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
|
||||
"low address bits of %V are meaningless",
|
||||
&value[1]);
|
||||
}
|
||||
|
||||
cidr = ngx_array_push(&ecf->debug_connection);
|
||||
if (cidr == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
*cidr = c;
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
ngx_memzero(&u, sizeof(ngx_url_t));
|
||||
u.host = value[1];
|
||||
|
||||
if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {
|
||||
if (u.err) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"%s in debug_connection \"%V\"",
|
||||
u.err, &u.host);
|
||||
}
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
cidr = ngx_array_push_n(&ecf->debug_connection, u.naddrs);
|
||||
if (cidr == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ngx_memzero(cidr, u.naddrs * sizeof(ngx_cidr_t));
|
||||
|
||||
for (i = 0; i < u.naddrs; i++) {
|
||||
cidr[i].family = u.addrs[i].sockaddr->sa_family;
|
||||
|
||||
switch (cidr[i].family) {
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
sin6 = (struct sockaddr_in6 *) u.addrs[i].sockaddr;
|
||||
cidr[i].u.in6.addr = sin6->sin6_addr;
|
||||
ngx_memset(cidr[i].u.in6.mask.s6_addr, 0xff, 16);
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: /* AF_INET */
|
||||
sin = (struct sockaddr_in *) u.addrs[i].sockaddr;
|
||||
cidr[i].u.in.addr = sin->sin_addr.s_addr;
|
||||
cidr[i].u.in.mask = 0xffffffff;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
|
||||
"\"debug_connection\" is ignored, you need to rebuild "
|
||||
"nginx using --with-debug option to enable it");
|
||||
|
||||
#endif
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_event_core_create_conf(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_event_conf_t *ecf;
|
||||
|
||||
ecf = ngx_palloc(cycle->pool, sizeof(ngx_event_conf_t));
|
||||
if (ecf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecf->connections = NGX_CONF_UNSET_UINT;
|
||||
ecf->use = NGX_CONF_UNSET_UINT;
|
||||
ecf->multi_accept = NGX_CONF_UNSET;
|
||||
ecf->accept_mutex = NGX_CONF_UNSET;
|
||||
ecf->accept_mutex_delay = NGX_CONF_UNSET_MSEC;
|
||||
ecf->name = (void *) NGX_CONF_UNSET;
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
|
||||
if (ngx_array_init(&ecf->debug_connection, cycle->pool, 4,
|
||||
sizeof(ngx_cidr_t)) == NGX_ERROR)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return ecf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_event_conf_t *ecf = conf;
|
||||
|
||||
#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
|
||||
int fd;
|
||||
#endif
|
||||
ngx_int_t i;
|
||||
ngx_module_t *module;
|
||||
ngx_event_module_t *event_module;
|
||||
|
||||
module = NULL;
|
||||
|
||||
#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
|
||||
|
||||
fd = epoll_create(100);
|
||||
|
||||
if (fd != -1) {
|
||||
(void) close(fd);
|
||||
module = &ngx_epoll_module;
|
||||
|
||||
} else if (ngx_errno != NGX_ENOSYS) {
|
||||
module = &ngx_epoll_module;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_DEVPOLL) && !(NGX_TEST_BUILD_DEVPOLL)
|
||||
|
||||
module = &ngx_devpoll_module;
|
||||
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_KQUEUE)
|
||||
|
||||
module = &ngx_kqueue_module;
|
||||
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_SELECT)
|
||||
|
||||
if (module == NULL) {
|
||||
module = &ngx_select_module;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (module == NULL) {
|
||||
for (i = 0; cycle->modules[i]; i++) {
|
||||
|
||||
if (cycle->modules[i]->type != NGX_EVENT_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
event_module = cycle->modules[i]->ctx;
|
||||
|
||||
if (ngx_strcmp(event_module->name->data, event_core_name.data) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
module = cycle->modules[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (module == NULL) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no events module found");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ngx_conf_init_uint_value(ecf->connections, DEFAULT_CONNECTIONS);
|
||||
cycle->connection_n = ecf->connections;
|
||||
|
||||
ngx_conf_init_uint_value(ecf->use, module->ctx_index);
|
||||
|
||||
event_module = module->ctx;
|
||||
ngx_conf_init_ptr_value(ecf->name, event_module->name->data);
|
||||
|
||||
ngx_conf_init_value(ecf->multi_accept, 0);
|
||||
ngx_conf_init_value(ecf->accept_mutex, 0);
|
||||
ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_EVENT_H_INCLUDED_
|
||||
#define _NGX_EVENT_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
#define NGX_INVALID_INDEX 0xd0d0d0d0
|
||||
|
||||
|
||||
#if (NGX_HAVE_IOCP)
|
||||
|
||||
typedef struct {
|
||||
WSAOVERLAPPED ovlp;
|
||||
ngx_event_t *event;
|
||||
int error;
|
||||
} ngx_event_ovlp_t;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
struct ngx_event_s {
|
||||
void *data;
|
||||
|
||||
unsigned write:1;
|
||||
|
||||
unsigned accept:1;
|
||||
|
||||
/* used to detect the stale events in kqueue and epoll */
|
||||
unsigned instance:1;
|
||||
|
||||
/*
|
||||
* the event was passed or would be passed to a kernel;
|
||||
* in aio mode - operation was posted.
|
||||
*/
|
||||
unsigned active:1;
|
||||
|
||||
unsigned disabled:1;
|
||||
|
||||
/* the ready event; in aio mode 0 means that no operation can be posted */
|
||||
unsigned ready:1;
|
||||
|
||||
unsigned oneshot:1;
|
||||
|
||||
/* aio operation is complete */
|
||||
unsigned complete:1;
|
||||
|
||||
unsigned eof:1;
|
||||
unsigned error:1;
|
||||
|
||||
unsigned timedout:1;
|
||||
unsigned timer_set:1;
|
||||
|
||||
unsigned delayed:1;
|
||||
|
||||
unsigned deferred_accept:1;
|
||||
|
||||
/* the pending eof reported by kqueue, epoll or in aio chain operation */
|
||||
unsigned pending_eof:1;
|
||||
|
||||
unsigned posted:1;
|
||||
|
||||
unsigned closed:1;
|
||||
|
||||
/* to test on worker exit */
|
||||
unsigned channel:1;
|
||||
unsigned resolver:1;
|
||||
|
||||
unsigned cancelable:1;
|
||||
|
||||
#if (NGX_HAVE_KQUEUE)
|
||||
unsigned kq_vnode:1;
|
||||
|
||||
/* the pending errno reported by kqueue */
|
||||
int kq_errno;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* kqueue only:
|
||||
* accept: number of sockets that wait to be accepted
|
||||
* read: bytes to read when event is ready
|
||||
* or lowat when event is set with NGX_LOWAT_EVENT flag
|
||||
* write: available space in buffer when event is ready
|
||||
* or lowat when event is set with NGX_LOWAT_EVENT flag
|
||||
*
|
||||
* iocp: TODO
|
||||
*
|
||||
* otherwise:
|
||||
* accept: 1 if accept many, 0 otherwise
|
||||
* read: bytes to read when event is ready, -1 if not known
|
||||
*/
|
||||
|
||||
int available;
|
||||
|
||||
ngx_event_handler_pt handler;
|
||||
|
||||
|
||||
#if (NGX_HAVE_IOCP)
|
||||
ngx_event_ovlp_t ovlp;
|
||||
#endif
|
||||
|
||||
ngx_uint_t index;
|
||||
|
||||
ngx_log_t *log;
|
||||
|
||||
ngx_rbtree_node_t timer;
|
||||
|
||||
/* the posted queue */
|
||||
ngx_queue_t queue;
|
||||
|
||||
#if 0
|
||||
|
||||
/* the threads support */
|
||||
|
||||
/*
|
||||
* the event thread context, we store it here
|
||||
* if $(CC) does not understand __thread declaration
|
||||
* and pthread_getspecific() is too costly
|
||||
*/
|
||||
|
||||
void *thr_ctx;
|
||||
|
||||
#if (NGX_EVENT_T_PADDING)
|
||||
|
||||
/* event should not cross cache line in SMP */
|
||||
|
||||
uint32_t padding[NGX_EVENT_T_PADDING];
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
#if (NGX_HAVE_FILE_AIO)
|
||||
|
||||
struct ngx_event_aio_s {
|
||||
void *data;
|
||||
ngx_event_handler_pt handler;
|
||||
ngx_file_t *file;
|
||||
|
||||
ngx_fd_t fd;
|
||||
|
||||
#if (NGX_HAVE_EVENTFD)
|
||||
int64_t res;
|
||||
#endif
|
||||
|
||||
#if !(NGX_HAVE_EVENTFD) || (NGX_TEST_BUILD_EPOLL)
|
||||
ngx_err_t err;
|
||||
size_t nbytes;
|
||||
#endif
|
||||
|
||||
ngx_aiocb_t aiocb;
|
||||
ngx_event_t event;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
|
||||
ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
|
||||
|
||||
ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
|
||||
ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
|
||||
|
||||
ngx_int_t (*add_conn)(ngx_connection_t *c);
|
||||
ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
|
||||
|
||||
ngx_int_t (*notify)(ngx_event_handler_pt handler);
|
||||
|
||||
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
|
||||
ngx_uint_t flags);
|
||||
|
||||
ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
|
||||
void (*done)(ngx_cycle_t *cycle);
|
||||
} ngx_event_actions_t;
|
||||
|
||||
|
||||
extern ngx_event_actions_t ngx_event_actions;
|
||||
#if (NGX_HAVE_EPOLLRDHUP)
|
||||
extern ngx_uint_t ngx_use_epoll_rdhup;
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* The event filter requires to read/write the whole data:
|
||||
* select, poll, /dev/poll, kqueue, epoll.
|
||||
*/
|
||||
#define NGX_USE_LEVEL_EVENT 0x00000001
|
||||
|
||||
/*
|
||||
* The event filter is deleted after a notification without an additional
|
||||
* syscall: kqueue, epoll.
|
||||
*/
|
||||
#define NGX_USE_ONESHOT_EVENT 0x00000002
|
||||
|
||||
/*
|
||||
* The event filter notifies only the changes and an initial level:
|
||||
* kqueue, epoll.
|
||||
*/
|
||||
#define NGX_USE_CLEAR_EVENT 0x00000004
|
||||
|
||||
/*
|
||||
* The event filter has kqueue features: the eof flag, errno,
|
||||
* available data, etc.
|
||||
*/
|
||||
#define NGX_USE_KQUEUE_EVENT 0x00000008
|
||||
|
||||
/*
|
||||
* The event filter supports low water mark: kqueue's NOTE_LOWAT.
|
||||
* kqueue in FreeBSD 4.1-4.2 has no NOTE_LOWAT so we need a separate flag.
|
||||
*/
|
||||
#define NGX_USE_LOWAT_EVENT 0x00000010
|
||||
|
||||
/*
|
||||
* The event filter requires to do i/o operation until EAGAIN: epoll.
|
||||
*/
|
||||
#define NGX_USE_GREEDY_EVENT 0x00000020
|
||||
|
||||
/*
|
||||
* The event filter is epoll.
|
||||
*/
|
||||
#define NGX_USE_EPOLL_EVENT 0x00000040
|
||||
|
||||
/*
|
||||
* Obsolete.
|
||||
*/
|
||||
#define NGX_USE_RTSIG_EVENT 0x00000080
|
||||
|
||||
/*
|
||||
* Obsolete.
|
||||
*/
|
||||
#define NGX_USE_AIO_EVENT 0x00000100
|
||||
|
||||
/*
|
||||
* Need to add socket or handle only once: i/o completion port.
|
||||
*/
|
||||
#define NGX_USE_IOCP_EVENT 0x00000200
|
||||
|
||||
/*
|
||||
* The event filter has no opaque data and requires file descriptors table:
|
||||
* poll, /dev/poll.
|
||||
*/
|
||||
#define NGX_USE_FD_EVENT 0x00000400
|
||||
|
||||
/*
|
||||
* The event module handles periodic or absolute timer event by itself:
|
||||
* kqueue in FreeBSD 4.4, NetBSD 2.0, and MacOSX 10.4, Solaris 10's event ports.
|
||||
*/
|
||||
#define NGX_USE_TIMER_EVENT 0x00000800
|
||||
|
||||
/*
|
||||
* All event filters on file descriptor are deleted after a notification:
|
||||
* Solaris 10's event ports.
|
||||
*/
|
||||
#define NGX_USE_EVENTPORT_EVENT 0x00001000
|
||||
|
||||
/*
|
||||
* The event filter support vnode notifications: kqueue.
|
||||
*/
|
||||
#define NGX_USE_VNODE_EVENT 0x00002000
|
||||
|
||||
|
||||
/*
|
||||
* The event filter is deleted just before the closing file.
|
||||
* Has no meaning for select and poll.
|
||||
* kqueue, epoll, eventport: allows to avoid explicit delete,
|
||||
* because filter automatically is deleted
|
||||
* on file close,
|
||||
*
|
||||
* /dev/poll: we need to flush POLLREMOVE event
|
||||
* before closing file.
|
||||
*/
|
||||
#define NGX_CLOSE_EVENT 1
|
||||
|
||||
/*
|
||||
* disable temporarily event filter, this may avoid locks
|
||||
* in kernel malloc()/free(): kqueue.
|
||||
*/
|
||||
#define NGX_DISABLE_EVENT 2
|
||||
|
||||
/*
|
||||
* event must be passed to kernel right now, do not wait until batch processing.
|
||||
*/
|
||||
#define NGX_FLUSH_EVENT 4
|
||||
|
||||
|
||||
/* these flags have a meaning only for kqueue */
|
||||
#define NGX_LOWAT_EVENT 0
|
||||
#define NGX_VNODE_EVENT 0
|
||||
|
||||
|
||||
#if (NGX_HAVE_EPOLL) && !(NGX_HAVE_EPOLLRDHUP)
|
||||
#define EPOLLRDHUP 0
|
||||
#endif
|
||||
|
||||
|
||||
#if (NGX_HAVE_KQUEUE)
|
||||
|
||||
#define NGX_READ_EVENT EVFILT_READ
|
||||
#define NGX_WRITE_EVENT EVFILT_WRITE
|
||||
|
||||
#undef NGX_VNODE_EVENT
|
||||
#define NGX_VNODE_EVENT EVFILT_VNODE
|
||||
|
||||
/*
|
||||
* NGX_CLOSE_EVENT, NGX_LOWAT_EVENT, and NGX_FLUSH_EVENT are the module flags
|
||||
* and they must not go into a kernel so we need to choose the value
|
||||
* that must not interfere with any existent and future kqueue flags.
|
||||
* kqueue has such values - EV_FLAG1, EV_EOF, and EV_ERROR:
|
||||
* they are reserved and cleared on a kernel entrance.
|
||||
*/
|
||||
#undef NGX_CLOSE_EVENT
|
||||
#define NGX_CLOSE_EVENT EV_EOF
|
||||
|
||||
#undef NGX_LOWAT_EVENT
|
||||
#define NGX_LOWAT_EVENT EV_FLAG1
|
||||
|
||||
#undef NGX_FLUSH_EVENT
|
||||
#define NGX_FLUSH_EVENT EV_ERROR
|
||||
|
||||
#define NGX_LEVEL_EVENT 0
|
||||
#define NGX_ONESHOT_EVENT EV_ONESHOT
|
||||
#define NGX_CLEAR_EVENT EV_CLEAR
|
||||
|
||||
#undef NGX_DISABLE_EVENT
|
||||
#define NGX_DISABLE_EVENT EV_DISABLE
|
||||
|
||||
|
||||
#elif (NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \
|
||||
|| (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT))
|
||||
|
||||
#define NGX_READ_EVENT POLLIN
|
||||
#define NGX_WRITE_EVENT POLLOUT
|
||||
|
||||
#define NGX_LEVEL_EVENT 0
|
||||
#define NGX_ONESHOT_EVENT 1
|
||||
|
||||
|
||||
#elif (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
|
||||
|
||||
#define NGX_READ_EVENT (EPOLLIN|EPOLLRDHUP)
|
||||
#define NGX_WRITE_EVENT EPOLLOUT
|
||||
|
||||
#define NGX_LEVEL_EVENT 0
|
||||
#define NGX_CLEAR_EVENT EPOLLET
|
||||
#define NGX_ONESHOT_EVENT 0x70000000
|
||||
#if 0
|
||||
#define NGX_ONESHOT_EVENT EPOLLONESHOT
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_EPOLLEXCLUSIVE)
|
||||
#define NGX_EXCLUSIVE_EVENT EPOLLEXCLUSIVE
|
||||
#endif
|
||||
|
||||
#elif (NGX_HAVE_POLL)
|
||||
|
||||
#define NGX_READ_EVENT POLLIN
|
||||
#define NGX_WRITE_EVENT POLLOUT
|
||||
|
||||
#define NGX_LEVEL_EVENT 0
|
||||
#define NGX_ONESHOT_EVENT 1
|
||||
|
||||
|
||||
#else /* select */
|
||||
|
||||
#define NGX_READ_EVENT 0
|
||||
#define NGX_WRITE_EVENT 1
|
||||
|
||||
#define NGX_LEVEL_EVENT 0
|
||||
#define NGX_ONESHOT_EVENT 1
|
||||
|
||||
#endif /* NGX_HAVE_KQUEUE */
|
||||
|
||||
|
||||
#if (NGX_HAVE_IOCP)
|
||||
#define NGX_IOCP_ACCEPT 0
|
||||
#define NGX_IOCP_IO 1
|
||||
#define NGX_IOCP_CONNECT 2
|
||||
#endif
|
||||
|
||||
|
||||
#if (NGX_TEST_BUILD_EPOLL)
|
||||
#define NGX_EXCLUSIVE_EVENT 0
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef NGX_CLEAR_EVENT
|
||||
#define NGX_CLEAR_EVENT 0 /* dummy declaration */
|
||||
#endif
|
||||
|
||||
|
||||
#define ngx_process_events ngx_event_actions.process_events
|
||||
#define ngx_done_events ngx_event_actions.done
|
||||
|
||||
#define ngx_add_event ngx_event_actions.add
|
||||
#define ngx_del_event ngx_event_actions.del
|
||||
#define ngx_add_conn ngx_event_actions.add_conn
|
||||
#define ngx_del_conn ngx_event_actions.del_conn
|
||||
|
||||
#define ngx_notify ngx_event_actions.notify
|
||||
|
||||
#define ngx_add_timer ngx_event_add_timer
|
||||
#define ngx_del_timer ngx_event_del_timer
|
||||
|
||||
|
||||
extern ngx_os_io_t ngx_io;
|
||||
|
||||
#define ngx_recv ngx_io.recv
|
||||
#define ngx_recv_chain ngx_io.recv_chain
|
||||
#define ngx_udp_recv ngx_io.udp_recv
|
||||
#define ngx_send ngx_io.send
|
||||
#define ngx_send_chain ngx_io.send_chain
|
||||
#define ngx_udp_send ngx_io.udp_send
|
||||
#define ngx_udp_send_chain ngx_io.udp_send_chain
|
||||
|
||||
|
||||
#define NGX_EVENT_MODULE 0x544E5645 /* "EVNT" */
|
||||
#define NGX_EVENT_CONF 0x02000000
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t connections;
|
||||
ngx_uint_t use;
|
||||
|
||||
ngx_flag_t multi_accept;
|
||||
ngx_flag_t accept_mutex;
|
||||
|
||||
ngx_msec_t accept_mutex_delay;
|
||||
|
||||
u_char *name;
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
ngx_array_t debug_connection;
|
||||
#endif
|
||||
} ngx_event_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t *name;
|
||||
|
||||
void *(*create_conf)(ngx_cycle_t *cycle);
|
||||
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
|
||||
|
||||
ngx_event_actions_t actions;
|
||||
} ngx_event_module_t;
|
||||
|
||||
|
||||
extern ngx_atomic_t *ngx_connection_counter;
|
||||
|
||||
extern ngx_atomic_t *ngx_accept_mutex_ptr;
|
||||
extern ngx_shmtx_t ngx_accept_mutex;
|
||||
extern ngx_uint_t ngx_use_accept_mutex;
|
||||
extern ngx_uint_t ngx_accept_events;
|
||||
extern ngx_uint_t ngx_accept_mutex_held;
|
||||
extern ngx_msec_t ngx_accept_mutex_delay;
|
||||
extern ngx_int_t ngx_accept_disabled;
|
||||
extern ngx_uint_t ngx_use_exclusive_accept;
|
||||
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
|
||||
extern ngx_atomic_t *ngx_stat_accepted;
|
||||
extern ngx_atomic_t *ngx_stat_handled;
|
||||
extern ngx_atomic_t *ngx_stat_requests;
|
||||
extern ngx_atomic_t *ngx_stat_active;
|
||||
extern ngx_atomic_t *ngx_stat_reading;
|
||||
extern ngx_atomic_t *ngx_stat_writing;
|
||||
extern ngx_atomic_t *ngx_stat_waiting;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#define NGX_UPDATE_TIME 1
|
||||
#define NGX_POST_EVENTS 2
|
||||
|
||||
|
||||
extern sig_atomic_t ngx_event_timer_alarm;
|
||||
extern ngx_uint_t ngx_event_flags;
|
||||
extern ngx_module_t ngx_events_module;
|
||||
extern ngx_module_t ngx_event_core_module;
|
||||
|
||||
|
||||
#define ngx_event_get_conf(conf_ctx, module) \
|
||||
(*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index]
|
||||
|
||||
|
||||
|
||||
void ngx_event_accept(ngx_event_t *ev);
|
||||
ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle);
|
||||
ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle);
|
||||
u_char *ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len);
|
||||
#if (NGX_DEBUG)
|
||||
void ngx_debug_accepted_connection(ngx_event_conf_t *ecf, ngx_connection_t *c);
|
||||
#endif
|
||||
|
||||
|
||||
void ngx_process_events_and_timers(ngx_cycle_t *cycle);
|
||||
ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags);
|
||||
ngx_int_t ngx_handle_write_event(ngx_event_t *wev, size_t lowat);
|
||||
|
||||
|
||||
#if (NGX_WIN32)
|
||||
void ngx_event_acceptex(ngx_event_t *ev);
|
||||
ngx_int_t ngx_event_post_acceptex(ngx_listening_t *ls, ngx_uint_t n);
|
||||
u_char *ngx_acceptex_log_error(ngx_log_t *log, u_char *buf, size_t len);
|
||||
#endif
|
||||
|
||||
|
||||
ngx_int_t ngx_send_lowat(ngx_connection_t *c, size_t lowat);
|
||||
|
||||
|
||||
/* used in ngx_log_debugX() */
|
||||
#define ngx_event_ident(p) ((ngx_connection_t *) (p))->fd
|
||||
|
||||
|
||||
#include <ngx_event_timer.h>
|
||||
#include <ngx_event_posted.h>
|
||||
#include <ngx_event_udp.h>
|
||||
|
||||
#if (NGX_WIN32)
|
||||
#include <ngx_iocp_module.h>
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* _NGX_EVENT_H_INCLUDED_ */
|
||||
@@ -0,0 +1,572 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
static ngx_int_t ngx_disable_accept_events(ngx_cycle_t *cycle, ngx_uint_t all);
|
||||
#if (NGX_HAVE_EPOLLEXCLUSIVE)
|
||||
static void ngx_reorder_accept_events(ngx_listening_t *ls);
|
||||
#endif
|
||||
static void ngx_close_accepted_connection(ngx_connection_t *c);
|
||||
|
||||
|
||||
void
|
||||
ngx_event_accept(ngx_event_t *ev)
|
||||
{
|
||||
socklen_t socklen;
|
||||
ngx_err_t err;
|
||||
ngx_log_t *log;
|
||||
ngx_uint_t level;
|
||||
ngx_socket_t s;
|
||||
ngx_event_t *rev, *wev;
|
||||
ngx_sockaddr_t sa;
|
||||
ngx_listening_t *ls;
|
||||
ngx_connection_t *c, *lc;
|
||||
ngx_event_conf_t *ecf;
|
||||
#if (NGX_HAVE_ACCEPT4)
|
||||
static ngx_uint_t use_accept4 = 1;
|
||||
#endif
|
||||
|
||||
if (ev->timedout) {
|
||||
if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev->timedout = 0;
|
||||
}
|
||||
|
||||
ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
|
||||
|
||||
if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
|
||||
ev->available = ecf->multi_accept;
|
||||
}
|
||||
|
||||
lc = ev->data;
|
||||
ls = lc->listening;
|
||||
ev->ready = 0;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"accept on %V, ready: %d", &ls->addr_text, ev->available);
|
||||
|
||||
do {
|
||||
socklen = sizeof(ngx_sockaddr_t);
|
||||
|
||||
#if (NGX_HAVE_ACCEPT4)
|
||||
if (use_accept4) {
|
||||
s = accept4(lc->fd, &sa.sockaddr, &socklen, SOCK_NONBLOCK);
|
||||
} else {
|
||||
s = accept(lc->fd, &sa.sockaddr, &socklen);
|
||||
}
|
||||
#else
|
||||
s = accept(lc->fd, &sa.sockaddr, &socklen);
|
||||
#endif
|
||||
|
||||
if (s == (ngx_socket_t) -1) {
|
||||
err = ngx_socket_errno;
|
||||
|
||||
if (err == NGX_EAGAIN) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
|
||||
"accept() not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
level = NGX_LOG_ALERT;
|
||||
|
||||
if (err == NGX_ECONNABORTED) {
|
||||
level = NGX_LOG_ERR;
|
||||
|
||||
} else if (err == NGX_EMFILE || err == NGX_ENFILE) {
|
||||
level = NGX_LOG_CRIT;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_ACCEPT4)
|
||||
ngx_log_error(level, ev->log, err,
|
||||
use_accept4 ? "accept4() failed" : "accept() failed");
|
||||
|
||||
if (use_accept4 && err == NGX_ENOSYS) {
|
||||
use_accept4 = 0;
|
||||
ngx_inherited_nonblocking = 0;
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
ngx_log_error(level, ev->log, err, "accept() failed");
|
||||
#endif
|
||||
|
||||
if (err == NGX_ECONNABORTED) {
|
||||
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
|
||||
ev->available--;
|
||||
}
|
||||
|
||||
if (ev->available) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (err == NGX_EMFILE || err == NGX_ENFILE) {
|
||||
if (ngx_disable_accept_events((ngx_cycle_t *) ngx_cycle, 1)
|
||||
!= NGX_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ngx_use_accept_mutex) {
|
||||
if (ngx_accept_mutex_held) {
|
||||
ngx_shmtx_unlock(&ngx_accept_mutex);
|
||||
ngx_accept_mutex_held = 0;
|
||||
}
|
||||
|
||||
ngx_accept_disabled = 1;
|
||||
|
||||
} else {
|
||||
ngx_add_timer(ev, ecf->accept_mutex_delay);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
(void) ngx_atomic_fetch_add(ngx_stat_accepted, 1);
|
||||
#endif
|
||||
|
||||
ngx_accept_disabled = ngx_cycle->connection_n / 8
|
||||
- ngx_cycle->free_connection_n;
|
||||
|
||||
c = ngx_get_connection(s, ev->log);
|
||||
|
||||
if (c == NULL) {
|
||||
if (ngx_close_socket(s) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
|
||||
ngx_close_socket_n " failed");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
c->type = SOCK_STREAM;
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
(void) ngx_atomic_fetch_add(ngx_stat_active, 1);
|
||||
#endif
|
||||
|
||||
c->pool = ngx_create_pool(ls->pool_size, ev->log);
|
||||
if (c->pool == NULL) {
|
||||
ngx_close_accepted_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
|
||||
socklen = sizeof(ngx_sockaddr_t);
|
||||
}
|
||||
|
||||
c->sockaddr = ngx_palloc(c->pool, socklen);
|
||||
if (c->sockaddr == NULL) {
|
||||
ngx_close_accepted_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_memcpy(c->sockaddr, &sa, socklen);
|
||||
|
||||
log = ngx_palloc(c->pool, sizeof(ngx_log_t));
|
||||
if (log == NULL) {
|
||||
ngx_close_accepted_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* set a blocking mode for iocp and non-blocking mode for others */
|
||||
|
||||
if (ngx_inherited_nonblocking) {
|
||||
if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
|
||||
if (ngx_blocking(s) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
|
||||
ngx_blocking_n " failed");
|
||||
ngx_close_accepted_connection(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
|
||||
if (ngx_nonblocking(s) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
|
||||
ngx_nonblocking_n " failed");
|
||||
ngx_close_accepted_connection(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*log = ls->log;
|
||||
|
||||
c->recv = ngx_recv;
|
||||
c->send = ngx_send;
|
||||
c->recv_chain = ngx_recv_chain;
|
||||
c->send_chain = ngx_send_chain;
|
||||
|
||||
c->log = log;
|
||||
c->pool->log = log;
|
||||
|
||||
c->socklen = socklen;
|
||||
c->listening = ls;
|
||||
c->local_sockaddr = ls->sockaddr;
|
||||
c->local_socklen = ls->socklen;
|
||||
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
if (c->sockaddr->sa_family == AF_UNIX) {
|
||||
c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
|
||||
c->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
|
||||
#if (NGX_SOLARIS)
|
||||
/* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */
|
||||
c->sendfile = 0;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
rev = c->read;
|
||||
wev = c->write;
|
||||
|
||||
wev->ready = 1;
|
||||
|
||||
if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
|
||||
rev->ready = 1;
|
||||
}
|
||||
|
||||
if (ev->deferred_accept) {
|
||||
rev->ready = 1;
|
||||
#if (NGX_HAVE_KQUEUE || NGX_HAVE_EPOLLRDHUP)
|
||||
rev->available = 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
rev->log = log;
|
||||
wev->log = log;
|
||||
|
||||
/*
|
||||
* TODO: MT: - ngx_atomic_fetch_add()
|
||||
* or protection by critical section or light mutex
|
||||
*
|
||||
* TODO: MP: - allocated in a shared memory
|
||||
* - ngx_atomic_fetch_add()
|
||||
* or protection by critical section or light mutex
|
||||
*/
|
||||
|
||||
c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
|
||||
|
||||
c->start_time = ngx_current_msec;
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
(void) ngx_atomic_fetch_add(ngx_stat_handled, 1);
|
||||
#endif
|
||||
|
||||
if (ls->addr_ntop) {
|
||||
c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
|
||||
if (c->addr_text.data == NULL) {
|
||||
ngx_close_accepted_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
|
||||
c->addr_text.data,
|
||||
ls->addr_text_max_len, 0);
|
||||
if (c->addr_text.len == 0) {
|
||||
ngx_close_accepted_connection(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
{
|
||||
ngx_str_t addr;
|
||||
u_char text[NGX_SOCKADDR_STRLEN];
|
||||
|
||||
ngx_debug_accepted_connection(ecf, c);
|
||||
|
||||
if (log->log_level & NGX_LOG_DEBUG_EVENT) {
|
||||
addr.data = text;
|
||||
addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text,
|
||||
NGX_SOCKADDR_STRLEN, 1);
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0,
|
||||
"*%uA accept: %V fd:%d", c->number, &addr, s);
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
|
||||
if (ngx_add_conn(c) == NGX_ERROR) {
|
||||
ngx_close_accepted_connection(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log->data = NULL;
|
||||
log->handler = NULL;
|
||||
|
||||
ls->handler(c);
|
||||
|
||||
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
|
||||
ev->available--;
|
||||
}
|
||||
|
||||
} while (ev->available);
|
||||
|
||||
#if (NGX_HAVE_EPOLLEXCLUSIVE)
|
||||
ngx_reorder_accept_events(ls);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
|
||||
{
|
||||
if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"accept mutex locked");
|
||||
|
||||
if (ngx_accept_mutex_held && ngx_accept_events == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
|
||||
ngx_shmtx_unlock(&ngx_accept_mutex);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_accept_events = 0;
|
||||
ngx_accept_mutex_held = 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"accept mutex lock failed: %ui", ngx_accept_mutex_held);
|
||||
|
||||
if (ngx_accept_mutex_held) {
|
||||
if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_accept_mutex_held = 0;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_enable_accept_events(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_listening_t *ls;
|
||||
ngx_connection_t *c;
|
||||
|
||||
ls = cycle->listening.elts;
|
||||
for (i = 0; i < cycle->listening.nelts; i++) {
|
||||
|
||||
c = ls[i].connection;
|
||||
|
||||
if (c == NULL || c->read->active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ngx_add_event(c->read, NGX_READ_EVENT, 0) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_disable_accept_events(ngx_cycle_t *cycle, ngx_uint_t all)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_listening_t *ls;
|
||||
ngx_connection_t *c;
|
||||
|
||||
ls = cycle->listening.elts;
|
||||
for (i = 0; i < cycle->listening.nelts; i++) {
|
||||
|
||||
c = ls[i].connection;
|
||||
|
||||
if (c == NULL || !c->read->active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_REUSEPORT)
|
||||
|
||||
/*
|
||||
* do not disable accept on worker's own sockets
|
||||
* when disabling accept events due to accept mutex
|
||||
*/
|
||||
|
||||
if (ls[i].reuseport && !all) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (ngx_del_event(c->read, NGX_READ_EVENT, NGX_DISABLE_EVENT)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#if (NGX_HAVE_EPOLLEXCLUSIVE)
|
||||
|
||||
static void
|
||||
ngx_reorder_accept_events(ngx_listening_t *ls)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
/*
|
||||
* Linux with EPOLLEXCLUSIVE usually notifies only the process which
|
||||
* was first to add the listening socket to the epoll instance. As
|
||||
* a result most of the connections are handled by the first worker
|
||||
* process. To fix this, we re-add the socket periodically, so other
|
||||
* workers will get a chance to accept connections.
|
||||
*/
|
||||
|
||||
if (!ngx_use_exclusive_accept) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_REUSEPORT)
|
||||
|
||||
if (ls->reuseport) {
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
c = ls->connection;
|
||||
|
||||
if (c->requests++ % 16 != 0
|
||||
&& ngx_accept_disabled <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ngx_del_event(c->read, NGX_READ_EVENT, NGX_DISABLE_EVENT)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ngx_add_event(c->read, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)
|
||||
== NGX_ERROR)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static void
|
||||
ngx_close_accepted_connection(ngx_connection_t *c)
|
||||
{
|
||||
ngx_socket_t fd;
|
||||
|
||||
ngx_free_connection(c);
|
||||
|
||||
fd = c->fd;
|
||||
c->fd = (ngx_socket_t) -1;
|
||||
|
||||
if (ngx_close_socket(fd) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
|
||||
ngx_close_socket_n " failed");
|
||||
}
|
||||
|
||||
if (c->pool) {
|
||||
ngx_destroy_pool(c->pool);
|
||||
}
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
u_char *
|
||||
ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len)
|
||||
{
|
||||
return ngx_snprintf(buf, len, " while accepting new connection on %V",
|
||||
log->data);
|
||||
}
|
||||
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
|
||||
void
|
||||
ngx_debug_accepted_connection(ngx_event_conf_t *ecf, ngx_connection_t *c)
|
||||
{
|
||||
struct sockaddr_in *sin;
|
||||
ngx_cidr_t *cidr;
|
||||
ngx_uint_t i;
|
||||
#if (NGX_HAVE_INET6)
|
||||
struct sockaddr_in6 *sin6;
|
||||
ngx_uint_t n;
|
||||
#endif
|
||||
|
||||
cidr = ecf->debug_connection.elts;
|
||||
for (i = 0; i < ecf->debug_connection.nelts; i++) {
|
||||
if (cidr[i].family != (ngx_uint_t) c->sockaddr->sa_family) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
switch (cidr[i].family) {
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
sin6 = (struct sockaddr_in6 *) c->sockaddr;
|
||||
for (n = 0; n < 16; n++) {
|
||||
if ((sin6->sin6_addr.s6_addr[n]
|
||||
& cidr[i].u.in6.mask.s6_addr[n])
|
||||
!= cidr[i].u.in6.addr.s6_addr[n])
|
||||
{
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
case AF_UNIX:
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: /* AF_INET */
|
||||
sin = (struct sockaddr_in *) c->sockaddr;
|
||||
if ((sin->sin_addr.s_addr & cidr[i].u.in.mask)
|
||||
!= cidr[i].u.in.addr)
|
||||
{
|
||||
goto next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
c->log->log_level = NGX_LOG_DEBUG_CONNECTION|NGX_LOG_DEBUG_ALL;
|
||||
break;
|
||||
|
||||
next:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,229 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
static void ngx_close_posted_connection(ngx_connection_t *c);
|
||||
|
||||
|
||||
void
|
||||
ngx_event_acceptex(ngx_event_t *rev)
|
||||
{
|
||||
ngx_listening_t *ls;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = rev->data;
|
||||
ls = c->listening;
|
||||
|
||||
c->log->handler = ngx_accept_log_error;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "AcceptEx: %d", c->fd);
|
||||
|
||||
if (rev->ovlp.error) {
|
||||
ngx_log_error(NGX_LOG_CRIT, c->log, rev->ovlp.error,
|
||||
"AcceptEx() %V failed", &ls->addr_text);
|
||||
return;
|
||||
}
|
||||
|
||||
/* SO_UPDATE_ACCEPT_CONTEXT is required for shutdown() to work */
|
||||
|
||||
if (setsockopt(c->fd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
|
||||
(char *) &ls->fd, sizeof(ngx_socket_t))
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno,
|
||||
"setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed for %V",
|
||||
&c->addr_text);
|
||||
/* TODO: close socket */
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_getacceptexsockaddrs(c->buffer->pos,
|
||||
ls->post_accept_buffer_size,
|
||||
ls->socklen + 16,
|
||||
ls->socklen + 16,
|
||||
&c->local_sockaddr, &c->local_socklen,
|
||||
&c->sockaddr, &c->socklen);
|
||||
|
||||
if (ls->post_accept_buffer_size) {
|
||||
c->buffer->last += rev->available;
|
||||
c->buffer->end = c->buffer->start + ls->post_accept_buffer_size;
|
||||
|
||||
} else {
|
||||
c->buffer = NULL;
|
||||
}
|
||||
|
||||
if (ls->addr_ntop) {
|
||||
c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
|
||||
if (c->addr_text.data == NULL) {
|
||||
/* TODO: close socket */
|
||||
return;
|
||||
}
|
||||
|
||||
c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
|
||||
c->addr_text.data,
|
||||
ls->addr_text_max_len, 0);
|
||||
if (c->addr_text.len == 0) {
|
||||
/* TODO: close socket */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_event_post_acceptex(ls, 1);
|
||||
|
||||
c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
|
||||
|
||||
c->start_time = ngx_current_msec;
|
||||
|
||||
ls->handler(c);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_event_post_acceptex(ngx_listening_t *ls, ngx_uint_t n)
|
||||
{
|
||||
u_long rcvd;
|
||||
ngx_err_t err;
|
||||
ngx_log_t *log;
|
||||
ngx_uint_t i;
|
||||
ngx_event_t *rev, *wev;
|
||||
ngx_socket_t s;
|
||||
ngx_connection_t *c;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
|
||||
/* TODO: look up reused sockets */
|
||||
|
||||
s = ngx_socket(ls->sockaddr->sa_family, ls->type, 0);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &ls->log, 0,
|
||||
ngx_socket_n " s:%d", s);
|
||||
|
||||
if (s == (ngx_socket_t) -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, &ls->log, ngx_socket_errno,
|
||||
ngx_socket_n " failed");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c = ngx_get_connection(s, &ls->log);
|
||||
|
||||
if (c == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->pool = ngx_create_pool(ls->pool_size, &ls->log);
|
||||
if (c->pool == NULL) {
|
||||
ngx_close_posted_connection(c);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
log = ngx_palloc(c->pool, sizeof(ngx_log_t));
|
||||
if (log == NULL) {
|
||||
ngx_close_posted_connection(c);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->buffer = ngx_create_temp_buf(c->pool, ls->post_accept_buffer_size
|
||||
+ 2 * (ls->socklen + 16));
|
||||
if (c->buffer == NULL) {
|
||||
ngx_close_posted_connection(c);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->local_sockaddr = ngx_palloc(c->pool, ls->socklen);
|
||||
if (c->local_sockaddr == NULL) {
|
||||
ngx_close_posted_connection(c);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->sockaddr = ngx_palloc(c->pool, ls->socklen);
|
||||
if (c->sockaddr == NULL) {
|
||||
ngx_close_posted_connection(c);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*log = ls->log;
|
||||
c->log = log;
|
||||
|
||||
c->recv = ngx_recv;
|
||||
c->send = ngx_send;
|
||||
c->recv_chain = ngx_recv_chain;
|
||||
c->send_chain = ngx_send_chain;
|
||||
|
||||
c->listening = ls;
|
||||
|
||||
rev = c->read;
|
||||
wev = c->write;
|
||||
|
||||
rev->ovlp.event = rev;
|
||||
wev->ovlp.event = wev;
|
||||
rev->handler = ngx_event_acceptex;
|
||||
|
||||
rev->ready = 1;
|
||||
wev->ready = 1;
|
||||
|
||||
rev->log = c->log;
|
||||
wev->log = c->log;
|
||||
|
||||
if (ngx_add_event(rev, 0, NGX_IOCP_IO) == NGX_ERROR) {
|
||||
ngx_close_posted_connection(c);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_acceptex(ls->fd, s, c->buffer->pos, ls->post_accept_buffer_size,
|
||||
ls->socklen + 16, ls->socklen + 16,
|
||||
&rcvd, (LPOVERLAPPED) &rev->ovlp)
|
||||
== 0)
|
||||
{
|
||||
err = ngx_socket_errno;
|
||||
if (err != WSA_IO_PENDING) {
|
||||
ngx_log_error(NGX_LOG_ALERT, &ls->log, err,
|
||||
"AcceptEx() %V failed", &ls->addr_text);
|
||||
|
||||
ngx_close_posted_connection(c);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_close_posted_connection(ngx_connection_t *c)
|
||||
{
|
||||
ngx_socket_t fd;
|
||||
|
||||
ngx_free_connection(c);
|
||||
|
||||
fd = c->fd;
|
||||
c->fd = (ngx_socket_t) -1;
|
||||
|
||||
if (ngx_close_socket(fd) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
|
||||
ngx_close_socket_n " failed");
|
||||
}
|
||||
|
||||
if (c->pool) {
|
||||
ngx_destroy_pool(c->pool);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
u_char *
|
||||
ngx_acceptex_log_error(ngx_log_t *log, u_char *buf, size_t len)
|
||||
{
|
||||
return ngx_snprintf(buf, len, " while posting AcceptEx() on %V", log->data);
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
#include <ngx_event_connect.h>
|
||||
|
||||
|
||||
#if (NGX_HAVE_TRANSPARENT_PROXY)
|
||||
static ngx_int_t ngx_event_connect_set_transparent(ngx_peer_connection_t *pc,
|
||||
ngx_socket_t s);
|
||||
#endif
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_event_connect_peer(ngx_peer_connection_t *pc)
|
||||
{
|
||||
int rc, type, value;
|
||||
#if (NGX_HAVE_IP_BIND_ADDRESS_NO_PORT || NGX_LINUX)
|
||||
in_port_t port;
|
||||
#endif
|
||||
ngx_int_t event;
|
||||
ngx_err_t err;
|
||||
ngx_uint_t level;
|
||||
ngx_socket_t s;
|
||||
ngx_event_t *rev, *wev;
|
||||
ngx_connection_t *c;
|
||||
|
||||
rc = pc->get(pc, pc->data);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
type = (pc->type ? pc->type : SOCK_STREAM);
|
||||
|
||||
s = ngx_socket(pc->sockaddr->sa_family, type, 0);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, "%s socket %d",
|
||||
(type == SOCK_STREAM) ? "stream" : "dgram", s);
|
||||
|
||||
if (s == (ngx_socket_t) -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
ngx_socket_n " failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
c = ngx_get_connection(s, pc->log);
|
||||
|
||||
if (c == NULL) {
|
||||
if (ngx_close_socket(s) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
ngx_close_socket_n " failed");
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->type = type;
|
||||
|
||||
if (pc->rcvbuf) {
|
||||
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
|
||||
(const void *) &pc->rcvbuf, sizeof(int)) == -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
"setsockopt(SO_RCVBUF) failed");
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
if (pc->so_keepalive) {
|
||||
value = 1;
|
||||
|
||||
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
|
||||
(const void *) &value, sizeof(int))
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
"setsockopt(SO_KEEPALIVE) failed, ignored");
|
||||
}
|
||||
}
|
||||
|
||||
if (ngx_nonblocking(s) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
ngx_nonblocking_n " failed");
|
||||
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (pc->local) {
|
||||
|
||||
#if (NGX_HAVE_TRANSPARENT_PROXY)
|
||||
if (pc->transparent) {
|
||||
if (ngx_event_connect_set_transparent(pc, s) != NGX_OK) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_IP_BIND_ADDRESS_NO_PORT || NGX_LINUX)
|
||||
port = ngx_inet_get_port(pc->local->sockaddr);
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_IP_BIND_ADDRESS_NO_PORT)
|
||||
|
||||
if (pc->sockaddr->sa_family != AF_UNIX && port == 0) {
|
||||
static int bind_address_no_port = 1;
|
||||
|
||||
if (bind_address_no_port) {
|
||||
if (setsockopt(s, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT,
|
||||
(const void *) &bind_address_no_port,
|
||||
sizeof(int)) == -1)
|
||||
{
|
||||
err = ngx_socket_errno;
|
||||
|
||||
if (err != NGX_EOPNOTSUPP && err != NGX_ENOPROTOOPT) {
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, err,
|
||||
"setsockopt(IP_BIND_ADDRESS_NO_PORT) "
|
||||
"failed, ignored");
|
||||
|
||||
} else {
|
||||
bind_address_no_port = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if (NGX_LINUX)
|
||||
|
||||
if (pc->type == SOCK_DGRAM && port != 0) {
|
||||
int reuse_addr = 1;
|
||||
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
|
||||
(const void *) &reuse_addr, sizeof(int))
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
"setsockopt(SO_REUSEADDR) failed");
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (bind(s, pc->local->sockaddr, pc->local->socklen) == -1) {
|
||||
ngx_log_error(NGX_LOG_CRIT, pc->log, ngx_socket_errno,
|
||||
"bind(%V) failed", &pc->local->name);
|
||||
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == SOCK_STREAM) {
|
||||
c->recv = ngx_recv;
|
||||
c->send = ngx_send;
|
||||
c->recv_chain = ngx_recv_chain;
|
||||
c->send_chain = ngx_send_chain;
|
||||
|
||||
c->sendfile = 1;
|
||||
|
||||
if (pc->sockaddr->sa_family == AF_UNIX) {
|
||||
c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
|
||||
c->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
|
||||
|
||||
#if (NGX_SOLARIS)
|
||||
/* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */
|
||||
c->sendfile = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
} else { /* type == SOCK_DGRAM */
|
||||
c->recv = ngx_udp_recv;
|
||||
c->send = ngx_send;
|
||||
c->send_chain = ngx_udp_send_chain;
|
||||
|
||||
c->need_flush_buf = 1;
|
||||
}
|
||||
|
||||
c->log_error = pc->log_error;
|
||||
|
||||
rev = c->read;
|
||||
wev = c->write;
|
||||
|
||||
rev->log = pc->log;
|
||||
wev->log = pc->log;
|
||||
|
||||
pc->connection = c;
|
||||
|
||||
c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
|
||||
|
||||
c->start_time = ngx_current_msec;
|
||||
|
||||
if (ngx_add_conn) {
|
||||
if (ngx_add_conn(c) == NGX_ERROR) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0,
|
||||
"connect to %V, fd:%d #%uA", pc->name, s, c->number);
|
||||
|
||||
rc = connect(s, pc->sockaddr, pc->socklen);
|
||||
|
||||
if (rc == -1) {
|
||||
err = ngx_socket_errno;
|
||||
|
||||
|
||||
if (err != NGX_EINPROGRESS
|
||||
#if (NGX_WIN32)
|
||||
/* Winsock returns WSAEWOULDBLOCK (NGX_EAGAIN) */
|
||||
&& err != NGX_EAGAIN
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if (err == NGX_ECONNREFUSED
|
||||
#if (NGX_LINUX)
|
||||
/*
|
||||
* Linux returns EAGAIN instead of ECONNREFUSED
|
||||
* for unix sockets if listen queue is full
|
||||
*/
|
||||
|| err == NGX_EAGAIN
|
||||
#endif
|
||||
|| err == NGX_ECONNRESET
|
||||
|| err == NGX_ENETDOWN
|
||||
|| err == NGX_ENETUNREACH
|
||||
|| err == NGX_EHOSTDOWN
|
||||
|| err == NGX_EHOSTUNREACH)
|
||||
{
|
||||
level = NGX_LOG_ERR;
|
||||
|
||||
} else {
|
||||
level = NGX_LOG_CRIT;
|
||||
}
|
||||
|
||||
ngx_log_error(level, c->log, err, "connect() to %V failed",
|
||||
pc->name);
|
||||
|
||||
ngx_close_connection(c);
|
||||
pc->connection = NULL;
|
||||
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
}
|
||||
|
||||
if (ngx_add_conn) {
|
||||
if (rc == -1) {
|
||||
|
||||
/* NGX_EINPROGRESS */
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connected");
|
||||
|
||||
wev->ready = 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, ngx_socket_errno,
|
||||
"connect(): %d", rc);
|
||||
|
||||
if (ngx_blocking(s) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
ngx_blocking_n " failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/*
|
||||
* FreeBSD's aio allows to post an operation on non-connected socket.
|
||||
* NT does not support it.
|
||||
*
|
||||
* TODO: check in Win32, etc. As workaround we can use NGX_ONESHOT_EVENT
|
||||
*/
|
||||
|
||||
rev->ready = 1;
|
||||
wev->ready = 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
|
||||
|
||||
/* kqueue */
|
||||
|
||||
event = NGX_CLEAR_EVENT;
|
||||
|
||||
} else {
|
||||
|
||||
/* select, poll, /dev/poll */
|
||||
|
||||
event = NGX_LEVEL_EVENT;
|
||||
}
|
||||
|
||||
if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (rc == -1) {
|
||||
|
||||
/* NGX_EINPROGRESS */
|
||||
|
||||
if (ngx_add_event(wev, NGX_WRITE_EVENT, event) != NGX_OK) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connected");
|
||||
|
||||
wev->ready = 1;
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
failed:
|
||||
|
||||
ngx_close_connection(c);
|
||||
pc->connection = NULL;
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
#if (NGX_HAVE_TRANSPARENT_PROXY)
|
||||
|
||||
static ngx_int_t
|
||||
ngx_event_connect_set_transparent(ngx_peer_connection_t *pc, ngx_socket_t s)
|
||||
{
|
||||
int value;
|
||||
|
||||
value = 1;
|
||||
|
||||
#if defined(SO_BINDANY)
|
||||
|
||||
if (setsockopt(s, SOL_SOCKET, SO_BINDANY,
|
||||
(const void *) &value, sizeof(int)) == -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
"setsockopt(SO_BINDANY) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
switch (pc->local->sockaddr->sa_family) {
|
||||
|
||||
case AF_INET:
|
||||
|
||||
#if defined(IP_TRANSPARENT)
|
||||
|
||||
if (setsockopt(s, IPPROTO_IP, IP_TRANSPARENT,
|
||||
(const void *) &value, sizeof(int)) == -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
"setsockopt(IP_TRANSPARENT) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#elif defined(IP_BINDANY)
|
||||
|
||||
if (setsockopt(s, IPPROTO_IP, IP_BINDANY,
|
||||
(const void *) &value, sizeof(int)) == -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
"setsockopt(IP_BINDANY) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
break;
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
|
||||
case AF_INET6:
|
||||
|
||||
#if defined(IPV6_TRANSPARENT)
|
||||
|
||||
if (setsockopt(s, IPPROTO_IPV6, IPV6_TRANSPARENT,
|
||||
(const void *) &value, sizeof(int)) == -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
"setsockopt(IPV6_TRANSPARENT) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#elif defined(IPV6_BINDANY)
|
||||
|
||||
if (setsockopt(s, IPPROTO_IPV6, IPV6_BINDANY,
|
||||
(const void *) &value, sizeof(int)) == -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
|
||||
"setsockopt(IPV6_BINDANY) failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, pc->log, 0,
|
||||
"could not enable transparent proxying for IPv6 "
|
||||
"on this platform");
|
||||
|
||||
return NGX_ERROR;
|
||||
|
||||
#endif
|
||||
|
||||
break;
|
||||
|
||||
#endif /* NGX_HAVE_INET6 */
|
||||
|
||||
}
|
||||
|
||||
#endif /* SO_BINDANY */
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_event_get_peer(ngx_peer_connection_t *pc, void *data)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_EVENT_CONNECT_H_INCLUDED_
|
||||
#define _NGX_EVENT_CONNECT_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
#define NGX_PEER_KEEPALIVE 1
|
||||
#define NGX_PEER_NEXT 2
|
||||
#define NGX_PEER_FAILED 4
|
||||
|
||||
|
||||
typedef struct ngx_peer_connection_s ngx_peer_connection_t;
|
||||
|
||||
typedef ngx_int_t (*ngx_event_get_peer_pt)(ngx_peer_connection_t *pc,
|
||||
void *data);
|
||||
typedef void (*ngx_event_free_peer_pt)(ngx_peer_connection_t *pc, void *data,
|
||||
ngx_uint_t state);
|
||||
typedef void (*ngx_event_notify_peer_pt)(ngx_peer_connection_t *pc,
|
||||
void *data, ngx_uint_t type);
|
||||
typedef ngx_int_t (*ngx_event_set_peer_session_pt)(ngx_peer_connection_t *pc,
|
||||
void *data);
|
||||
typedef void (*ngx_event_save_peer_session_pt)(ngx_peer_connection_t *pc,
|
||||
void *data);
|
||||
|
||||
|
||||
struct ngx_peer_connection_s {
|
||||
ngx_connection_t *connection;
|
||||
|
||||
struct sockaddr *sockaddr;
|
||||
socklen_t socklen;
|
||||
ngx_str_t *name;
|
||||
|
||||
ngx_uint_t tries;
|
||||
ngx_msec_t start_time;
|
||||
|
||||
ngx_event_get_peer_pt get;
|
||||
ngx_event_free_peer_pt free;
|
||||
ngx_event_notify_peer_pt notify;
|
||||
void *data;
|
||||
|
||||
#if (NGX_SSL || NGX_COMPAT)
|
||||
ngx_event_set_peer_session_pt set_session;
|
||||
ngx_event_save_peer_session_pt save_session;
|
||||
#endif
|
||||
|
||||
ngx_addr_t *local;
|
||||
|
||||
int type;
|
||||
int rcvbuf;
|
||||
|
||||
ngx_log_t *log;
|
||||
|
||||
unsigned cached:1;
|
||||
unsigned transparent:1;
|
||||
unsigned so_keepalive:1;
|
||||
unsigned down:1;
|
||||
|
||||
/* ngx_connection_log_error_e */
|
||||
unsigned log_error:2;
|
||||
|
||||
NGX_COMPAT_BEGIN(2)
|
||||
NGX_COMPAT_END
|
||||
};
|
||||
|
||||
|
||||
ngx_int_t ngx_event_connect_peer(ngx_peer_connection_t *pc);
|
||||
ngx_int_t ngx_event_get_peer(ngx_peer_connection_t *pc, void *data);
|
||||
|
||||
|
||||
#endif /* _NGX_EVENT_CONNECT_H_INCLUDED_ */
|
||||
@@ -0,0 +1,206 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
#define NGX_MAX_PENDING_CONN 10
|
||||
|
||||
|
||||
static CRITICAL_SECTION connect_lock;
|
||||
static int nconnects;
|
||||
static ngx_connection_t pending_connects[NGX_MAX_PENDING_CONN];
|
||||
|
||||
static HANDLE pending_connect_event;
|
||||
|
||||
__declspec(thread) int nevents = 0;
|
||||
__declspec(thread) WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS + 1];
|
||||
__declspec(thread) ngx_connection_t *conn[WSA_MAXIMUM_WAIT_EVENTS + 1];
|
||||
|
||||
|
||||
|
||||
int ngx_iocp_wait_connect(ngx_connection_t *c)
|
||||
{
|
||||
for ( ;; ) {
|
||||
EnterCriticalSection(&connect_lock);
|
||||
|
||||
if (nconnects < NGX_MAX_PENDING_CONN) {
|
||||
pending_connects[--nconnects] = c;
|
||||
LeaveCriticalSection(&connect_lock);
|
||||
|
||||
if (SetEvent(pending_connect_event) == 0) {
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
|
||||
"SetEvent() failed");
|
||||
return NGX_ERROR;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&connect_lock);
|
||||
ngx_log_error(NGX_LOG_NOTICE, c->log, 0,
|
||||
"max number of pending connect()s is %d",
|
||||
NGX_MAX_PENDING_CONN);
|
||||
msleep(100);
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
if (ngx_iocp_new_thread(1) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
started = 1;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
int ngx_iocp_new_thread(int main)
|
||||
{
|
||||
u_int id;
|
||||
|
||||
if (main) {
|
||||
pending_connect_event = CreateEvent(NULL, 0, 1, NULL);
|
||||
if (pending_connect_event == INVALID_HANDLE_VALUE) {
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
|
||||
"CreateThread() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (CreateThread(NULL, 0, ngx_iocp_wait_events, main, 0, &id)
|
||||
== INVALID_HANDLE_VALUE)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
|
||||
"CreateThread() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
SetEvent(event) {
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
|
||||
"SetEvent() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
int ngx_iocp_new_connect()
|
||||
{
|
||||
EnterCriticalSection(&connect_lock);
|
||||
c = pending_connects[--nconnects];
|
||||
LeaveCriticalSection(&connect_lock);
|
||||
|
||||
conn[nevents] = c;
|
||||
|
||||
events[nevents] = WSACreateEvent();
|
||||
if (events[nevents] == INVALID_HANDLE_VALUE) {
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
|
||||
"WSACreateEvent() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (WSAEventSelect(c->fd, events[nevents], FD_CONNECT) == -1)
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
|
||||
"WSAEventSelect() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
nevents++;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
void ngx_iocp_wait_events(int main)
|
||||
{
|
||||
WSANETWORKEVENTS ne;
|
||||
|
||||
nevents = 1;
|
||||
events[0] = pending_connect_event;
|
||||
conn[0] = NULL;
|
||||
|
||||
for ( ;; ) {
|
||||
offset = (nevents == WSA_MAXIMUM_WAIT_EVENTS + 1) ? 1: 0;
|
||||
timeout = (nevents == 1 && !first) ? 60000: INFINITE;
|
||||
|
||||
n = WSAWaitForMultipleEvents(nevents - offset, events[offset],
|
||||
0, timeout, 0);
|
||||
if (n == WAIT_FAILED) {
|
||||
ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
|
||||
"WSAWaitForMultipleEvents() failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (n == WAIT_TIMEOUT) {
|
||||
if (nevents == 2 && !main) {
|
||||
ExitThread(0);
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, log, 0,
|
||||
"WSAWaitForMultipleEvents() "
|
||||
"returned unexpected WAIT_TIMEOUT");
|
||||
continue;
|
||||
}
|
||||
|
||||
n -= WSA_WAIT_EVENT_0;
|
||||
|
||||
if (events[n] == NULL) {
|
||||
|
||||
/* the pending_connect_event */
|
||||
|
||||
if (nevents == WSA_MAXIMUM_WAIT_EVENTS) {
|
||||
ngx_iocp_new_thread(0);
|
||||
} else {
|
||||
ngx_iocp_new_connect();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (WSAEnumNetworkEvents(c[n].fd, events[n], &ne) == -1) {
|
||||
ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
|
||||
"WSAEnumNetworkEvents() failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ne.lNetworkEvents & FD_CONNECT) {
|
||||
conn[n].write->ovlp.error = ne.iErrorCode[FD_CONNECT_BIT];
|
||||
|
||||
if (PostQueuedCompletionStatus(iocp, 0, NGX_IOCP_CONNECT,
|
||||
&conn[n].write->ovlp) == 0)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
|
||||
"PostQueuedCompletionStatus() failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (n < nevents) {
|
||||
conn[n] = conn[nevents];
|
||||
events[n] = events[nevents];
|
||||
}
|
||||
|
||||
nevents--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ne.lNetworkEvents & FD_ACCEPT) {
|
||||
|
||||
/* CHECK ERROR ??? */
|
||||
|
||||
ngx_event_post_acceptex(conn[n].listening, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, c[n].log, 0,
|
||||
"WSAWaitForMultipleEvents() "
|
||||
"returned unexpected network event %ul",
|
||||
ne.lNetworkEvents);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5949 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
#define NGX_SSL_PASSWORD_BUFFER_SIZE 4096
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t engine; /* unsigned engine:1; */
|
||||
} ngx_openssl_conf_t;
|
||||
|
||||
|
||||
static X509 *ngx_ssl_load_certificate(ngx_pool_t *pool, char **err,
|
||||
ngx_str_t *cert, STACK_OF(X509) **chain);
|
||||
static EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err,
|
||||
ngx_str_t *key, ngx_array_t *passwords);
|
||||
static int ngx_ssl_password_callback(char *buf, int size, int rwflag,
|
||||
void *userdata);
|
||||
static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store);
|
||||
static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where,
|
||||
int ret);
|
||||
static void ngx_ssl_passwords_cleanup(void *data);
|
||||
static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn,
|
||||
ngx_ssl_session_t *sess);
|
||||
#ifdef SSL_READ_EARLY_DATA_SUCCESS
|
||||
static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c);
|
||||
#endif
|
||||
#if (NGX_DEBUG)
|
||||
static void ngx_ssl_handshake_log(ngx_connection_t *c);
|
||||
#endif
|
||||
static void ngx_ssl_handshake_handler(ngx_event_t *ev);
|
||||
#ifdef SSL_READ_EARLY_DATA_SUCCESS
|
||||
static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf,
|
||||
size_t size);
|
||||
#endif
|
||||
static ngx_int_t ngx_ssl_handle_recv(ngx_connection_t *c, int n);
|
||||
static void ngx_ssl_write_handler(ngx_event_t *wev);
|
||||
#ifdef SSL_READ_EARLY_DATA_SUCCESS
|
||||
static ssize_t ngx_ssl_write_early(ngx_connection_t *c, u_char *data,
|
||||
size_t size);
|
||||
#endif
|
||||
static ssize_t ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file,
|
||||
size_t size);
|
||||
static void ngx_ssl_read_handler(ngx_event_t *rev);
|
||||
static void ngx_ssl_shutdown_handler(ngx_event_t *ev);
|
||||
static void ngx_ssl_connection_error(ngx_connection_t *c, int sslerr,
|
||||
ngx_err_t err, char *text);
|
||||
static void ngx_ssl_clear_error(ngx_log_t *log);
|
||||
|
||||
static ngx_int_t ngx_ssl_session_id_context(ngx_ssl_t *ssl,
|
||||
ngx_str_t *sess_ctx, ngx_array_t *certificates);
|
||||
static int ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn,
|
||||
ngx_ssl_session_t *sess);
|
||||
static ngx_ssl_session_t *ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn,
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100003L
|
||||
const
|
||||
#endif
|
||||
u_char *id, int len, int *copy);
|
||||
static void ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess);
|
||||
static void ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
|
||||
ngx_slab_pool_t *shpool, ngx_uint_t n);
|
||||
static void ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp,
|
||||
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
|
||||
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
|
||||
static int ngx_ssl_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
|
||||
unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
|
||||
HMAC_CTX *hctx, int enc);
|
||||
static ngx_int_t ngx_ssl_rotate_ticket_keys(SSL_CTX *ssl_ctx, ngx_log_t *log);
|
||||
static void ngx_ssl_ticket_keys_cleanup(void *data);
|
||||
#endif
|
||||
|
||||
#ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
|
||||
static ngx_int_t ngx_ssl_check_name(ngx_str_t *name, ASN1_STRING *str);
|
||||
#endif
|
||||
|
||||
static time_t ngx_ssl_parse_time(
|
||||
#if OPENSSL_VERSION_NUMBER > 0x10100000L
|
||||
const
|
||||
#endif
|
||||
ASN1_TIME *asn1time, ngx_log_t *log);
|
||||
|
||||
static void *ngx_openssl_create_conf(ngx_cycle_t *cycle);
|
||||
static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
|
||||
static void ngx_openssl_exit(ngx_cycle_t *cycle);
|
||||
|
||||
|
||||
static ngx_command_t ngx_openssl_commands[] = {
|
||||
|
||||
{ ngx_string("ssl_engine"),
|
||||
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_openssl_engine,
|
||||
0,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_core_module_t ngx_openssl_module_ctx = {
|
||||
ngx_string("openssl"),
|
||||
ngx_openssl_create_conf,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_openssl_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_openssl_module_ctx, /* module context */
|
||||
ngx_openssl_commands, /* module directives */
|
||||
NGX_CORE_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
ngx_openssl_exit, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
int ngx_ssl_connection_index;
|
||||
int ngx_ssl_server_conf_index;
|
||||
int ngx_ssl_session_cache_index;
|
||||
int ngx_ssl_ticket_keys_index;
|
||||
int ngx_ssl_ocsp_index;
|
||||
int ngx_ssl_certificate_index;
|
||||
int ngx_ssl_next_certificate_index;
|
||||
int ngx_ssl_certificate_name_index;
|
||||
int ngx_ssl_stapling_index;
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_init(ngx_log_t *log)
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100003L
|
||||
|
||||
if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0, "OPENSSL_init_ssl() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* OPENSSL_init_ssl() may leave errors in the error queue
|
||||
* while returning success
|
||||
*/
|
||||
|
||||
ERR_clear_error();
|
||||
|
||||
#else
|
||||
|
||||
OPENSSL_config(NULL);
|
||||
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef SSL_OP_NO_COMPRESSION
|
||||
{
|
||||
/*
|
||||
* Disable gzip compression in OpenSSL prior to 1.0.0 version,
|
||||
* this saves about 522K per connection.
|
||||
*/
|
||||
int n;
|
||||
STACK_OF(SSL_COMP) *ssl_comp_methods;
|
||||
|
||||
ssl_comp_methods = SSL_COMP_get_compression_methods();
|
||||
n = sk_SSL_COMP_num(ssl_comp_methods);
|
||||
|
||||
while (n--) {
|
||||
(void) sk_SSL_COMP_pop(ssl_comp_methods);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ngx_ssl_connection_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
||||
|
||||
if (ngx_ssl_connection_index == -1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0, "SSL_get_ex_new_index() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_ssl_server_conf_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
|
||||
NULL);
|
||||
if (ngx_ssl_server_conf_index == -1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0,
|
||||
"SSL_CTX_get_ex_new_index() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_ssl_session_cache_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
|
||||
NULL);
|
||||
if (ngx_ssl_session_cache_index == -1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0,
|
||||
"SSL_CTX_get_ex_new_index() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_ssl_ticket_keys_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
|
||||
NULL);
|
||||
if (ngx_ssl_ticket_keys_index == -1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0,
|
||||
"SSL_CTX_get_ex_new_index() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_ssl_ocsp_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
||||
if (ngx_ssl_ocsp_index == -1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0,
|
||||
"SSL_CTX_get_ex_new_index() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_ssl_certificate_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
|
||||
NULL);
|
||||
if (ngx_ssl_certificate_index == -1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0,
|
||||
"SSL_CTX_get_ex_new_index() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_ssl_next_certificate_index = X509_get_ex_new_index(0, NULL, NULL, NULL,
|
||||
NULL);
|
||||
if (ngx_ssl_next_certificate_index == -1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_ssl_certificate_name_index = X509_get_ex_new_index(0, NULL, NULL, NULL,
|
||||
NULL);
|
||||
|
||||
if (ngx_ssl_certificate_name_index == -1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_ssl_stapling_index = X509_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
||||
|
||||
if (ngx_ssl_stapling_index == -1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data)
|
||||
{
|
||||
ssl->ctx = SSL_CTX_new(SSLv23_method());
|
||||
|
||||
if (ssl->ctx == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "SSL_CTX_new() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_server_conf_index, data) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set_ex_data() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, NULL) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set_ex_data() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ssl->buffer_size = NGX_SSL_BUFSIZE;
|
||||
|
||||
/* client side options */
|
||||
|
||||
#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_SESS_ID_BUG);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NETSCAPE_CHALLENGE_BUG);
|
||||
#endif
|
||||
|
||||
/* server side options */
|
||||
|
||||
#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLEAY_080_CLIENT_DH_BUG);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_TLS_D5_BUG
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_D5_BUG);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_BLOCK_PADDING_BUG);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
|
||||
#endif
|
||||
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_DH_USE);
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x009080dfL
|
||||
/* only in 0.9.8m+ */
|
||||
SSL_CTX_clear_options(ssl->ctx,
|
||||
SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1);
|
||||
#endif
|
||||
|
||||
if (!(protocols & NGX_SSL_SSLv2)) {
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv2);
|
||||
}
|
||||
if (!(protocols & NGX_SSL_SSLv3)) {
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv3);
|
||||
}
|
||||
if (!(protocols & NGX_SSL_TLSv1)) {
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1);
|
||||
}
|
||||
#ifdef SSL_OP_NO_TLSv1_1
|
||||
SSL_CTX_clear_options(ssl->ctx, SSL_OP_NO_TLSv1_1);
|
||||
if (!(protocols & NGX_SSL_TLSv1_1)) {
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_1);
|
||||
}
|
||||
#endif
|
||||
#ifdef SSL_OP_NO_TLSv1_2
|
||||
SSL_CTX_clear_options(ssl->ctx, SSL_OP_NO_TLSv1_2);
|
||||
if (!(protocols & NGX_SSL_TLSv1_2)) {
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_2);
|
||||
}
|
||||
#endif
|
||||
#ifdef SSL_OP_NO_TLSv1_3
|
||||
SSL_CTX_clear_options(ssl->ctx, SSL_OP_NO_TLSv1_3);
|
||||
if (!(protocols & NGX_SSL_TLSv1_3)) {
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_3);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SSL_CTX_set_min_proto_version
|
||||
SSL_CTX_set_min_proto_version(ssl->ctx, 0);
|
||||
SSL_CTX_set_max_proto_version(ssl->ctx, TLS1_2_VERSION);
|
||||
#endif
|
||||
|
||||
#ifdef TLS1_3_VERSION
|
||||
SSL_CTX_set_min_proto_version(ssl->ctx, 0);
|
||||
SSL_CTX_set_max_proto_version(ssl->ctx, TLS1_3_VERSION);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NO_COMPRESSION
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_COMPRESSION);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NO_ANTI_REPLAY
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_ANTI_REPLAY);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_IGNORE_UNEXPECTED_EOF);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_MODE_RELEASE_BUFFERS
|
||||
SSL_CTX_set_mode(ssl->ctx, SSL_MODE_RELEASE_BUFFERS);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_MODE_NO_AUTO_CHAIN
|
||||
SSL_CTX_set_mode(ssl->ctx, SSL_MODE_NO_AUTO_CHAIN);
|
||||
#endif
|
||||
|
||||
SSL_CTX_set_read_ahead(ssl->ctx, 1);
|
||||
|
||||
SSL_CTX_set_info_callback(ssl->ctx, ngx_ssl_info_callback);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *certs,
|
||||
ngx_array_t *keys, ngx_array_t *passwords)
|
||||
{
|
||||
ngx_str_t *cert, *key;
|
||||
ngx_uint_t i;
|
||||
|
||||
cert = certs->elts;
|
||||
key = keys->elts;
|
||||
|
||||
for (i = 0; i < certs->nelts; i++) {
|
||||
|
||||
if (ngx_ssl_certificate(cf, ssl, &cert[i], &key[i], passwords)
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
|
||||
ngx_str_t *key, ngx_array_t *passwords)
|
||||
{
|
||||
char *err;
|
||||
X509 *x509;
|
||||
EVP_PKEY *pkey;
|
||||
STACK_OF(X509) *chain;
|
||||
|
||||
x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain);
|
||||
if (x509 == NULL) {
|
||||
if (err != NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"cannot load certificate \"%s\": %s",
|
||||
cert->data, err);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_use_certificate(\"%s\") failed", cert->data);
|
||||
X509_free(x509);
|
||||
sk_X509_pop_free(chain, X509_free);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (X509_set_ex_data(x509, ngx_ssl_certificate_name_index, cert->data)
|
||||
== 0)
|
||||
{
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed");
|
||||
X509_free(x509);
|
||||
sk_X509_pop_free(chain, X509_free);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (X509_set_ex_data(x509, ngx_ssl_next_certificate_index,
|
||||
SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index))
|
||||
== 0)
|
||||
{
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed");
|
||||
X509_free(x509);
|
||||
sk_X509_pop_free(chain, X509_free);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set_ex_data() failed");
|
||||
X509_free(x509);
|
||||
sk_X509_pop_free(chain, X509_free);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that x509 is not freed here, but will be instead freed in
|
||||
* ngx_ssl_cleanup_ctx(). This is because we need to preserve all
|
||||
* certificates to be able to iterate all of them through exdata
|
||||
* (ngx_ssl_certificate_index, ngx_ssl_next_certificate_index),
|
||||
* while OpenSSL can free a certificate if it is replaced with another
|
||||
* certificate of the same type.
|
||||
*/
|
||||
|
||||
#ifdef SSL_CTX_set0_chain
|
||||
|
||||
if (SSL_CTX_set0_chain(ssl->ctx, chain) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set0_chain(\"%s\") failed", cert->data);
|
||||
sk_X509_pop_free(chain, X509_free);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#else
|
||||
{
|
||||
int n;
|
||||
|
||||
/* SSL_CTX_set0_chain() is only available in OpenSSL 1.0.2+ */
|
||||
|
||||
n = sk_X509_num(chain);
|
||||
|
||||
while (n--) {
|
||||
x509 = sk_X509_shift(chain);
|
||||
|
||||
if (SSL_CTX_add_extra_chain_cert(ssl->ctx, x509) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_add_extra_chain_cert(\"%s\") failed",
|
||||
cert->data);
|
||||
sk_X509_pop_free(chain, X509_free);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
sk_X509_free(chain);
|
||||
}
|
||||
#endif
|
||||
|
||||
pkey = ngx_ssl_load_certificate_key(cf->pool, &err, key, passwords);
|
||||
if (pkey == NULL) {
|
||||
if (err != NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"cannot load certificate key \"%s\": %s",
|
||||
key->data, err);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_PrivateKey(ssl->ctx, pkey) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_use_PrivateKey(\"%s\") failed", key->data);
|
||||
EVP_PKEY_free(pkey);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
EVP_PKEY_free(pkey);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords)
|
||||
{
|
||||
char *err;
|
||||
X509 *x509;
|
||||
EVP_PKEY *pkey;
|
||||
STACK_OF(X509) *chain;
|
||||
|
||||
x509 = ngx_ssl_load_certificate(pool, &err, cert, &chain);
|
||||
if (x509 == NULL) {
|
||||
if (err != NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
||||
"cannot load certificate \"%s\": %s",
|
||||
cert->data, err);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_use_certificate(c->ssl->connection, x509) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
||||
"SSL_use_certificate(\"%s\") failed", cert->data);
|
||||
X509_free(x509);
|
||||
sk_X509_pop_free(chain, X509_free);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
X509_free(x509);
|
||||
|
||||
#ifdef SSL_set0_chain
|
||||
|
||||
/*
|
||||
* SSL_set0_chain() is only available in OpenSSL 1.0.2+,
|
||||
* but this function is only called via certificate callback,
|
||||
* which is only available in OpenSSL 1.0.2+ as well
|
||||
*/
|
||||
|
||||
if (SSL_set0_chain(c->ssl->connection, chain) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
||||
"SSL_set0_chain(\"%s\") failed", cert->data);
|
||||
sk_X509_pop_free(chain, X509_free);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
pkey = ngx_ssl_load_certificate_key(pool, &err, key, passwords);
|
||||
if (pkey == NULL) {
|
||||
if (err != NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
||||
"cannot load certificate key \"%s\": %s",
|
||||
key->data, err);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_use_PrivateKey(c->ssl->connection, pkey) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
||||
"SSL_use_PrivateKey(\"%s\") failed", key->data);
|
||||
EVP_PKEY_free(pkey);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
EVP_PKEY_free(pkey);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static X509 *
|
||||
ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, ngx_str_t *cert,
|
||||
STACK_OF(X509) **chain)
|
||||
{
|
||||
BIO *bio;
|
||||
X509 *x509, *temp;
|
||||
u_long n;
|
||||
|
||||
if (ngx_strncmp(cert->data, "data:", sizeof("data:") - 1) == 0) {
|
||||
|
||||
bio = BIO_new_mem_buf(cert->data + sizeof("data:") - 1,
|
||||
cert->len - (sizeof("data:") - 1));
|
||||
if (bio == NULL) {
|
||||
*err = "BIO_new_mem_buf() failed";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, cert)
|
||||
!= NGX_OK)
|
||||
{
|
||||
*err = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bio = BIO_new_file((char *) cert->data, "r");
|
||||
if (bio == NULL) {
|
||||
*err = "BIO_new_file() failed";
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* certificate itself */
|
||||
|
||||
x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
|
||||
if (x509 == NULL) {
|
||||
*err = "PEM_read_bio_X509_AUX() failed";
|
||||
BIO_free(bio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* rest of the chain */
|
||||
|
||||
*chain = sk_X509_new_null();
|
||||
if (*chain == NULL) {
|
||||
*err = "sk_X509_new_null() failed";
|
||||
BIO_free(bio);
|
||||
X509_free(x509);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
temp = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||
if (temp == NULL) {
|
||||
n = ERR_peek_last_error();
|
||||
|
||||
if (ERR_GET_LIB(n) == ERR_LIB_PEM
|
||||
&& ERR_GET_REASON(n) == PEM_R_NO_START_LINE)
|
||||
{
|
||||
/* end of file */
|
||||
ERR_clear_error();
|
||||
break;
|
||||
}
|
||||
|
||||
/* some real error */
|
||||
|
||||
*err = "PEM_read_bio_X509() failed";
|
||||
BIO_free(bio);
|
||||
X509_free(x509);
|
||||
sk_X509_pop_free(*chain, X509_free);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (sk_X509_push(*chain, temp) == 0) {
|
||||
*err = "sk_X509_push() failed";
|
||||
BIO_free(bio);
|
||||
X509_free(x509);
|
||||
sk_X509_pop_free(*chain, X509_free);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
return x509;
|
||||
}
|
||||
|
||||
|
||||
static EVP_PKEY *
|
||||
ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err,
|
||||
ngx_str_t *key, ngx_array_t *passwords)
|
||||
{
|
||||
BIO *bio;
|
||||
EVP_PKEY *pkey;
|
||||
ngx_str_t *pwd;
|
||||
ngx_uint_t tries;
|
||||
pem_password_cb *cb;
|
||||
|
||||
if (ngx_strncmp(key->data, "engine:", sizeof("engine:") - 1) == 0) {
|
||||
|
||||
#ifndef OPENSSL_NO_ENGINE
|
||||
|
||||
u_char *p, *last;
|
||||
ENGINE *engine;
|
||||
|
||||
p = key->data + sizeof("engine:") - 1;
|
||||
last = (u_char *) ngx_strchr(p, ':');
|
||||
|
||||
if (last == NULL) {
|
||||
*err = "invalid syntax";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*last = '\0';
|
||||
|
||||
engine = ENGINE_by_id((char *) p);
|
||||
|
||||
if (engine == NULL) {
|
||||
*err = "ENGINE_by_id() failed";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*last++ = ':';
|
||||
|
||||
pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0);
|
||||
|
||||
if (pkey == NULL) {
|
||||
*err = "ENGINE_load_private_key() failed";
|
||||
ENGINE_free(engine);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ENGINE_free(engine);
|
||||
|
||||
return pkey;
|
||||
|
||||
#else
|
||||
|
||||
*err = "loading \"engine:...\" certificate keys is not supported";
|
||||
return NULL;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ngx_strncmp(key->data, "data:", sizeof("data:") - 1) == 0) {
|
||||
|
||||
bio = BIO_new_mem_buf(key->data + sizeof("data:") - 1,
|
||||
key->len - (sizeof("data:") - 1));
|
||||
if (bio == NULL) {
|
||||
*err = "BIO_new_mem_buf() failed";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, key)
|
||||
!= NGX_OK)
|
||||
{
|
||||
*err = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bio = BIO_new_file((char *) key->data, "r");
|
||||
if (bio == NULL) {
|
||||
*err = "BIO_new_file() failed";
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (passwords) {
|
||||
tries = passwords->nelts;
|
||||
pwd = passwords->elts;
|
||||
cb = ngx_ssl_password_callback;
|
||||
|
||||
} else {
|
||||
tries = 1;
|
||||
pwd = NULL;
|
||||
cb = NULL;
|
||||
}
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd);
|
||||
if (pkey != NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (tries-- > 1) {
|
||||
ERR_clear_error();
|
||||
(void) BIO_reset(bio);
|
||||
pwd++;
|
||||
continue;
|
||||
}
|
||||
|
||||
*err = "PEM_read_bio_PrivateKey() failed";
|
||||
BIO_free(bio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
return pkey;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
ngx_ssl_password_callback(char *buf, int size, int rwflag, void *userdata)
|
||||
{
|
||||
ngx_str_t *pwd = userdata;
|
||||
|
||||
if (rwflag) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
|
||||
"ngx_ssl_password_callback() is called for encryption");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (pwd == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (pwd->len > (size_t) size) {
|
||||
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
|
||||
"password is truncated to %d bytes", size);
|
||||
} else {
|
||||
size = pwd->len;
|
||||
}
|
||||
|
||||
ngx_memcpy(buf, pwd->data, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers,
|
||||
ngx_uint_t prefer_server_ciphers)
|
||||
{
|
||||
if (SSL_CTX_set_cipher_list(ssl->ctx, (char *) ciphers->data) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set_cipher_list(\"%V\") failed",
|
||||
ciphers);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (prefer_server_ciphers) {
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
|
||||
ngx_int_t depth)
|
||||
{
|
||||
STACK_OF(X509_NAME) *list;
|
||||
|
||||
SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_ssl_verify_callback);
|
||||
|
||||
SSL_CTX_set_verify_depth(ssl->ctx, depth);
|
||||
|
||||
if (cert->len == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
|
||||
== 0)
|
||||
{
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_load_verify_locations(\"%s\") failed",
|
||||
cert->data);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSL_CTX_load_verify_locations() may leave errors in the error queue
|
||||
* while returning success
|
||||
*/
|
||||
|
||||
ERR_clear_error();
|
||||
|
||||
list = SSL_load_client_CA_file((char *) cert->data);
|
||||
|
||||
if (list == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_load_client_CA_file(\"%s\") failed", cert->data);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
SSL_CTX_set_client_CA_list(ssl->ctx, list);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
|
||||
ngx_int_t depth)
|
||||
{
|
||||
SSL_CTX_set_verify(ssl->ctx, SSL_CTX_get_verify_mode(ssl->ctx),
|
||||
ngx_ssl_verify_callback);
|
||||
|
||||
SSL_CTX_set_verify_depth(ssl->ctx, depth);
|
||||
|
||||
if (cert->len == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
|
||||
== 0)
|
||||
{
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_load_verify_locations(\"%s\") failed",
|
||||
cert->data);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSL_CTX_load_verify_locations() may leave errors in the error queue
|
||||
* while returning success
|
||||
*/
|
||||
|
||||
ERR_clear_error();
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl)
|
||||
{
|
||||
X509_STORE *store;
|
||||
X509_LOOKUP *lookup;
|
||||
|
||||
if (crl->len == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_conf_full_name(cf->cycle, crl, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
store = SSL_CTX_get_cert_store(ssl->ctx);
|
||||
|
||||
if (store == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_get_cert_store() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
|
||||
|
||||
if (lookup == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"X509_STORE_add_lookup() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (X509_LOOKUP_load_file(lookup, (char *) crl->data, X509_FILETYPE_PEM)
|
||||
== 0)
|
||||
{
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"X509_LOOKUP_load_file(\"%s\") failed", crl->data);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
X509_STORE_set_flags(store,
|
||||
X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store)
|
||||
{
|
||||
#if (NGX_DEBUG)
|
||||
char *subject, *issuer;
|
||||
int err, depth;
|
||||
X509 *cert;
|
||||
X509_NAME *sname, *iname;
|
||||
ngx_connection_t *c;
|
||||
ngx_ssl_conn_t *ssl_conn;
|
||||
|
||||
ssl_conn = X509_STORE_CTX_get_ex_data(x509_store,
|
||||
SSL_get_ex_data_X509_STORE_CTX_idx());
|
||||
|
||||
c = ngx_ssl_get_connection(ssl_conn);
|
||||
|
||||
if (!(c->log->log_level & NGX_LOG_DEBUG_EVENT)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
cert = X509_STORE_CTX_get_current_cert(x509_store);
|
||||
err = X509_STORE_CTX_get_error(x509_store);
|
||||
depth = X509_STORE_CTX_get_error_depth(x509_store);
|
||||
|
||||
sname = X509_get_subject_name(cert);
|
||||
|
||||
if (sname) {
|
||||
subject = X509_NAME_oneline(sname, NULL, 0);
|
||||
if (subject == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0,
|
||||
"X509_NAME_oneline() failed");
|
||||
}
|
||||
|
||||
} else {
|
||||
subject = NULL;
|
||||
}
|
||||
|
||||
iname = X509_get_issuer_name(cert);
|
||||
|
||||
if (iname) {
|
||||
issuer = X509_NAME_oneline(iname, NULL, 0);
|
||||
if (issuer == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0,
|
||||
"X509_NAME_oneline() failed");
|
||||
}
|
||||
|
||||
} else {
|
||||
issuer = NULL;
|
||||
}
|
||||
|
||||
ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"verify:%d, error:%d, depth:%d, "
|
||||
"subject:\"%s\", issuer:\"%s\"",
|
||||
ok, err, depth,
|
||||
subject ? subject : "(none)",
|
||||
issuer ? issuer : "(none)");
|
||||
|
||||
if (subject) {
|
||||
OPENSSL_free(subject);
|
||||
}
|
||||
|
||||
if (issuer) {
|
||||
OPENSSL_free(issuer);
|
||||
}
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret)
|
||||
{
|
||||
BIO *rbio, *wbio;
|
||||
ngx_connection_t *c;
|
||||
|
||||
#ifndef SSL_OP_NO_RENEGOTIATION
|
||||
|
||||
if ((where & SSL_CB_HANDSHAKE_START)
|
||||
&& SSL_is_server((ngx_ssl_conn_t *) ssl_conn))
|
||||
{
|
||||
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
|
||||
|
||||
if (c->ssl->handshaked) {
|
||||
c->ssl->renegotiation = 1;
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL renegotiation");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef TLS1_3_VERSION
|
||||
|
||||
if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP
|
||||
&& SSL_version(ssl_conn) == TLS1_3_VERSION)
|
||||
{
|
||||
time_t now, time, timeout, conf_timeout;
|
||||
SSL_SESSION *sess;
|
||||
|
||||
/*
|
||||
* OpenSSL with TLSv1.3 updates the session creation time on
|
||||
* session resumption and keeps the session timeout unmodified,
|
||||
* making it possible to maintain the session forever, bypassing
|
||||
* client certificate expiration and revocation. To make sure
|
||||
* session timeouts are actually used, we now update the session
|
||||
* creation time and reduce the session timeout accordingly.
|
||||
*
|
||||
* BoringSSL with TLSv1.3 ignores configured session timeouts
|
||||
* and uses a hardcoded timeout instead, 7 days. So we update
|
||||
* session timeout to the configured value as soon as a session
|
||||
* is created.
|
||||
*/
|
||||
|
||||
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
|
||||
sess = SSL_get0_session(ssl_conn);
|
||||
|
||||
if (!c->ssl->session_timeout_set && sess) {
|
||||
c->ssl->session_timeout_set = 1;
|
||||
|
||||
now = ngx_time();
|
||||
time = SSL_SESSION_get_time(sess);
|
||||
timeout = SSL_SESSION_get_timeout(sess);
|
||||
conf_timeout = SSL_CTX_get_timeout(c->ssl->session_ctx);
|
||||
|
||||
timeout = ngx_min(timeout, conf_timeout);
|
||||
|
||||
if (now - time >= timeout) {
|
||||
SSL_SESSION_set1_id_context(sess, (unsigned char *) "", 0);
|
||||
|
||||
} else {
|
||||
SSL_SESSION_set_time(sess, now);
|
||||
SSL_SESSION_set_timeout(sess, timeout - (now - time));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) {
|
||||
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
|
||||
|
||||
if (!c->ssl->handshake_buffer_set) {
|
||||
/*
|
||||
* By default OpenSSL uses 4k buffer during a handshake,
|
||||
* which is too low for long certificate chains and might
|
||||
* result in extra round-trips.
|
||||
*
|
||||
* To adjust a buffer size we detect that buffering was added
|
||||
* to write side of the connection by comparing rbio and wbio.
|
||||
* If they are different, we assume that it's due to buffering
|
||||
* added to wbio, and set buffer size.
|
||||
*/
|
||||
|
||||
rbio = SSL_get_rbio(ssl_conn);
|
||||
wbio = SSL_get_wbio(ssl_conn);
|
||||
|
||||
if (rbio != wbio) {
|
||||
(void) BIO_set_write_buffer_size(wbio, NGX_SSL_BUFSIZE);
|
||||
c->ssl->handshake_buffer_set = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_array_t *
|
||||
ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file)
|
||||
{
|
||||
u_char *p, *last, *end;
|
||||
size_t len;
|
||||
ssize_t n;
|
||||
ngx_fd_t fd;
|
||||
ngx_str_t *pwd;
|
||||
ngx_array_t *passwords;
|
||||
ngx_pool_cleanup_t *cln;
|
||||
u_char buf[NGX_SSL_PASSWORD_BUFFER_SIZE];
|
||||
|
||||
if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
passwords = ngx_array_create(cf->temp_pool, 4, sizeof(ngx_str_t));
|
||||
if (passwords == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cln = ngx_pool_cleanup_add(cf->temp_pool, 0);
|
||||
if (cln == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cln->handler = ngx_ssl_passwords_cleanup;
|
||||
cln->data = passwords;
|
||||
|
||||
fd = ngx_open_file(file->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
|
||||
|
||||
if (fd == NGX_INVALID_FILE) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
|
||||
ngx_open_file_n " \"%s\" failed", file->data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
len = 0;
|
||||
last = buf;
|
||||
|
||||
do {
|
||||
n = ngx_read_fd(fd, last, NGX_SSL_PASSWORD_BUFFER_SIZE - len);
|
||||
|
||||
if (n == -1) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
|
||||
ngx_read_fd_n " \"%s\" failed", file->data);
|
||||
passwords = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
end = last + n;
|
||||
|
||||
if (len && n == 0) {
|
||||
*end++ = LF;
|
||||
}
|
||||
|
||||
p = buf;
|
||||
|
||||
for ( ;; ) {
|
||||
last = ngx_strlchr(last, end, LF);
|
||||
|
||||
if (last == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
len = last++ - p;
|
||||
|
||||
if (len && p[len - 1] == CR) {
|
||||
len--;
|
||||
}
|
||||
|
||||
if (len) {
|
||||
pwd = ngx_array_push(passwords);
|
||||
if (pwd == NULL) {
|
||||
passwords = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
pwd->len = len;
|
||||
pwd->data = ngx_pnalloc(cf->temp_pool, len);
|
||||
|
||||
if (pwd->data == NULL) {
|
||||
passwords->nelts--;
|
||||
passwords = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ngx_memcpy(pwd->data, p, len);
|
||||
}
|
||||
|
||||
p = last;
|
||||
}
|
||||
|
||||
len = end - p;
|
||||
|
||||
if (len == NGX_SSL_PASSWORD_BUFFER_SIZE) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"too long line in \"%s\"", file->data);
|
||||
passwords = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ngx_memmove(buf, p, len);
|
||||
last = buf + len;
|
||||
|
||||
} while (n != 0);
|
||||
|
||||
if (passwords->nelts == 0) {
|
||||
pwd = ngx_array_push(passwords);
|
||||
if (pwd == NULL) {
|
||||
passwords = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ngx_memzero(pwd, sizeof(ngx_str_t));
|
||||
}
|
||||
|
||||
cleanup:
|
||||
|
||||
if (ngx_close_file(fd) == NGX_FILE_ERROR) {
|
||||
ngx_conf_log_error(NGX_LOG_ALERT, cf, ngx_errno,
|
||||
ngx_close_file_n " \"%s\" failed", file->data);
|
||||
}
|
||||
|
||||
ngx_explicit_memzero(buf, NGX_SSL_PASSWORD_BUFFER_SIZE);
|
||||
|
||||
return passwords;
|
||||
}
|
||||
|
||||
|
||||
ngx_array_t *
|
||||
ngx_ssl_preserve_passwords(ngx_conf_t *cf, ngx_array_t *passwords)
|
||||
{
|
||||
ngx_str_t *opwd, *pwd;
|
||||
ngx_uint_t i;
|
||||
ngx_array_t *pwds;
|
||||
ngx_pool_cleanup_t *cln;
|
||||
static ngx_array_t empty_passwords;
|
||||
|
||||
if (passwords == NULL) {
|
||||
|
||||
/*
|
||||
* If there are no passwords, an empty array is used
|
||||
* to make sure OpenSSL's default password callback
|
||||
* won't block on reading from stdin.
|
||||
*/
|
||||
|
||||
return &empty_passwords;
|
||||
}
|
||||
|
||||
/*
|
||||
* Passwords are normally allocated from the temporary pool
|
||||
* and cleared after parsing configuration. To be used at
|
||||
* runtime they have to be copied to the configuration pool.
|
||||
*/
|
||||
|
||||
pwds = ngx_array_create(cf->pool, passwords->nelts, sizeof(ngx_str_t));
|
||||
if (pwds == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cln = ngx_pool_cleanup_add(cf->pool, 0);
|
||||
if (cln == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cln->handler = ngx_ssl_passwords_cleanup;
|
||||
cln->data = pwds;
|
||||
|
||||
opwd = passwords->elts;
|
||||
|
||||
for (i = 0; i < passwords->nelts; i++) {
|
||||
|
||||
pwd = ngx_array_push(pwds);
|
||||
if (pwd == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pwd->len = opwd[i].len;
|
||||
pwd->data = ngx_pnalloc(cf->pool, pwd->len);
|
||||
|
||||
if (pwd->data == NULL) {
|
||||
pwds->nelts--;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ngx_memcpy(pwd->data, opwd[i].data, opwd[i].len);
|
||||
}
|
||||
|
||||
return pwds;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_passwords_cleanup(void *data)
|
||||
{
|
||||
ngx_array_t *passwords = data;
|
||||
|
||||
ngx_str_t *pwd;
|
||||
ngx_uint_t i;
|
||||
|
||||
pwd = passwords->elts;
|
||||
|
||||
for (i = 0; i < passwords->nelts; i++) {
|
||||
ngx_explicit_memzero(pwd[i].data, pwd[i].len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file)
|
||||
{
|
||||
BIO *bio;
|
||||
|
||||
if (file->len == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
bio = BIO_new_file((char *) file->data, "r");
|
||||
if (bio == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"BIO_new_file(\"%s\") failed", file->data);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#ifdef SSL_CTX_set_tmp_dh
|
||||
{
|
||||
DH *dh;
|
||||
|
||||
dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
|
||||
if (dh == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"PEM_read_bio_DHparams(\"%s\") failed", file->data);
|
||||
BIO_free(bio);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_CTX_set_tmp_dh(ssl->ctx, dh) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set_tmp_dh(\"%s\") failed", file->data);
|
||||
DH_free(dh);
|
||||
BIO_free(bio);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
DH_free(dh);
|
||||
}
|
||||
#else
|
||||
{
|
||||
EVP_PKEY *dh;
|
||||
|
||||
/*
|
||||
* PEM_read_bio_DHparams() and SSL_CTX_set_tmp_dh()
|
||||
* are deprecated in OpenSSL 3.0
|
||||
*/
|
||||
|
||||
dh = PEM_read_bio_Parameters(bio, NULL);
|
||||
if (dh == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"PEM_read_bio_Parameters(\"%s\") failed", file->data);
|
||||
BIO_free(bio);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_CTX_set0_tmp_dh_pkey(ssl->ctx, dh) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set0_tmp_dh_pkey(\%s\") failed", file->data);
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x3000001fL)
|
||||
EVP_PKEY_free(dh);
|
||||
#endif
|
||||
BIO_free(bio);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name)
|
||||
{
|
||||
#ifndef OPENSSL_NO_ECDH
|
||||
|
||||
/*
|
||||
* Elliptic-Curve Diffie-Hellman parameters are either "named curves"
|
||||
* from RFC 4492 section 5.1.1, or explicitly described curves over
|
||||
* binary fields. OpenSSL only supports the "named curves", which provide
|
||||
* maximum interoperability.
|
||||
*/
|
||||
|
||||
#if (defined SSL_CTX_set1_curves_list || defined SSL_CTRL_SET_CURVES_LIST)
|
||||
|
||||
/*
|
||||
* OpenSSL 1.0.2+ allows configuring a curve list instead of a single
|
||||
* curve previously supported. By default an internal list is used,
|
||||
* with prime256v1 being preferred by server in OpenSSL 1.0.2b+
|
||||
* and X25519 in OpenSSL 1.1.0+.
|
||||
*
|
||||
* By default a curve preferred by the client will be used for
|
||||
* key exchange. The SSL_OP_CIPHER_SERVER_PREFERENCE option can
|
||||
* be used to prefer server curves instead, similar to what it
|
||||
* does for ciphers.
|
||||
*/
|
||||
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_ECDH_USE);
|
||||
|
||||
#ifdef SSL_CTRL_SET_ECDH_AUTO
|
||||
/* not needed in OpenSSL 1.1.0+ */
|
||||
(void) SSL_CTX_set_ecdh_auto(ssl->ctx, 1);
|
||||
#endif
|
||||
|
||||
if (ngx_strcmp(name->data, "auto") == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (SSL_CTX_set1_curves_list(ssl->ctx, (char *) name->data) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set1_curves_list(\"%s\") failed", name->data);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int nid;
|
||||
char *curve;
|
||||
EC_KEY *ecdh;
|
||||
|
||||
if (ngx_strcmp(name->data, "auto") == 0) {
|
||||
curve = "prime256v1";
|
||||
|
||||
} else {
|
||||
curve = (char *) name->data;
|
||||
}
|
||||
|
||||
nid = OBJ_sn2nid(curve);
|
||||
if (nid == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"OBJ_sn2nid(\"%s\") failed: unknown curve", curve);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ecdh = EC_KEY_new_by_curve_name(nid);
|
||||
if (ecdh == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"EC_KEY_new_by_curve_name(\"%s\") failed", curve);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_ECDH_USE);
|
||||
|
||||
SSL_CTX_set_tmp_ecdh(ssl->ctx, ecdh);
|
||||
|
||||
EC_KEY_free(ecdh);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable)
|
||||
{
|
||||
if (!enable) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#ifdef SSL_ERROR_EARLY_DATA_REJECTED
|
||||
|
||||
/* BoringSSL */
|
||||
|
||||
SSL_CTX_set_early_data_enabled(ssl->ctx, 1);
|
||||
|
||||
#elif defined SSL_READ_EARLY_DATA_SUCCESS
|
||||
|
||||
/* OpenSSL */
|
||||
|
||||
SSL_CTX_set_max_early_data(ssl->ctx, NGX_SSL_BUFSIZE);
|
||||
|
||||
#else
|
||||
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
|
||||
"\"ssl_early_data\" is not supported on this platform, "
|
||||
"ignored");
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_conf_commands(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *commands)
|
||||
{
|
||||
if (commands == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#ifdef SSL_CONF_FLAG_FILE
|
||||
{
|
||||
int type;
|
||||
u_char *key, *value;
|
||||
ngx_uint_t i;
|
||||
ngx_keyval_t *cmd;
|
||||
SSL_CONF_CTX *cctx;
|
||||
|
||||
cctx = SSL_CONF_CTX_new();
|
||||
if (cctx == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CONF_CTX_new() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE);
|
||||
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SERVER);
|
||||
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CLIENT);
|
||||
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE);
|
||||
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SHOW_ERRORS);
|
||||
|
||||
SSL_CONF_CTX_set_ssl_ctx(cctx, ssl->ctx);
|
||||
|
||||
cmd = commands->elts;
|
||||
for (i = 0; i < commands->nelts; i++) {
|
||||
|
||||
key = cmd[i].key.data;
|
||||
type = SSL_CONF_cmd_value_type(cctx, (char *) key);
|
||||
|
||||
if (type == SSL_CONF_TYPE_FILE || type == SSL_CONF_TYPE_DIR) {
|
||||
if (ngx_conf_full_name(cf->cycle, &cmd[i].value, 1) != NGX_OK) {
|
||||
SSL_CONF_CTX_free(cctx);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
value = cmd[i].value.data;
|
||||
|
||||
if (SSL_CONF_cmd(cctx, (char *) key, (char *) value) <= 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CONF_cmd(\"%s\", \"%s\") failed", key, value);
|
||||
SSL_CONF_CTX_free(cctx);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (SSL_CONF_CTX_finish(cctx) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CONF_finish() failed");
|
||||
SSL_CONF_CTX_free(cctx);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
SSL_CONF_CTX_free(cctx);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
#else
|
||||
ngx_log_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CONF_cmd() is not available on this platform");
|
||||
return NGX_ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable)
|
||||
{
|
||||
if (!enable) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
SSL_CTX_set_session_cache_mode(ssl->ctx,
|
||||
SSL_SESS_CACHE_CLIENT
|
||||
|SSL_SESS_CACHE_NO_INTERNAL);
|
||||
|
||||
SSL_CTX_sess_set_new_cb(ssl->ctx, ngx_ssl_new_client_session);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ngx_ssl_get_connection(ssl_conn);
|
||||
|
||||
if (c->ssl->save_session) {
|
||||
c->ssl->session = sess;
|
||||
|
||||
c->ssl->save_session(c);
|
||||
|
||||
c->ssl->session = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags)
|
||||
{
|
||||
ngx_ssl_connection_t *sc;
|
||||
|
||||
sc = ngx_pcalloc(c->pool, sizeof(ngx_ssl_connection_t));
|
||||
if (sc == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
sc->buffer = ((flags & NGX_SSL_BUFFER) != 0);
|
||||
sc->buffer_size = ssl->buffer_size;
|
||||
|
||||
sc->session_ctx = ssl->ctx;
|
||||
|
||||
#ifdef SSL_READ_EARLY_DATA_SUCCESS
|
||||
if (SSL_CTX_get_max_early_data(ssl->ctx)) {
|
||||
sc->try_early_data = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
sc->connection = SSL_new(ssl->ctx);
|
||||
|
||||
if (sc->connection == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_new() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_set_fd(sc->connection, c->fd) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_fd() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (flags & NGX_SSL_CLIENT) {
|
||||
SSL_set_connect_state(sc->connection);
|
||||
|
||||
} else {
|
||||
SSL_set_accept_state(sc->connection);
|
||||
|
||||
#ifdef SSL_OP_NO_RENEGOTIATION
|
||||
SSL_set_options(sc->connection, SSL_OP_NO_RENEGOTIATION);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (SSL_set_ex_data(sc->connection, ngx_ssl_connection_index, c) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->ssl = sc;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_ssl_session_t *
|
||||
ngx_ssl_get_session(ngx_connection_t *c)
|
||||
{
|
||||
#ifdef TLS1_3_VERSION
|
||||
if (c->ssl->session) {
|
||||
SSL_SESSION_up_ref(c->ssl->session);
|
||||
return c->ssl->session;
|
||||
}
|
||||
#endif
|
||||
|
||||
return SSL_get1_session(c->ssl->connection);
|
||||
}
|
||||
|
||||
|
||||
ngx_ssl_session_t *
|
||||
ngx_ssl_get0_session(ngx_connection_t *c)
|
||||
{
|
||||
if (c->ssl->session) {
|
||||
return c->ssl->session;
|
||||
}
|
||||
|
||||
return SSL_get0_session(c->ssl->connection);
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_set_session(ngx_connection_t *c, ngx_ssl_session_t *session)
|
||||
{
|
||||
if (session) {
|
||||
if (SSL_set_session(c->ssl->connection, session) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_session() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_handshake(ngx_connection_t *c)
|
||||
{
|
||||
int n, sslerr;
|
||||
ngx_err_t err;
|
||||
ngx_int_t rc;
|
||||
|
||||
#ifdef SSL_READ_EARLY_DATA_SUCCESS
|
||||
if (c->ssl->try_early_data) {
|
||||
return ngx_ssl_try_early_data(c);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (c->ssl->in_ocsp) {
|
||||
return ngx_ssl_ocsp_validate(c);
|
||||
}
|
||||
|
||||
ngx_ssl_clear_error(c->log);
|
||||
|
||||
n = SSL_do_handshake(c->ssl->connection);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
|
||||
|
||||
if (n == 1) {
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
ngx_ssl_handshake_log(c);
|
||||
#endif
|
||||
|
||||
c->recv = ngx_ssl_recv;
|
||||
c->send = ngx_ssl_write;
|
||||
c->recv_chain = ngx_ssl_recv_chain;
|
||||
c->send_chain = ngx_ssl_send_chain;
|
||||
|
||||
c->read->ready = 1;
|
||||
c->write->ready = 1;
|
||||
|
||||
#ifndef SSL_OP_NO_RENEGOTIATION
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
#ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS
|
||||
|
||||
/* initial handshake done, disable renegotiation (CVE-2009-3555) */
|
||||
if (c->ssl->connection->s3 && SSL_is_server(c->ssl->connection)) {
|
||||
c->ssl->connection->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined BIO_get_ktls_send && !NGX_WIN32)
|
||||
|
||||
if (BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)) == 1) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"BIO_get_ktls_send(): 1");
|
||||
c->ssl->sendfile = 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
rc = ngx_ssl_ocsp_validate(c);
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (rc == NGX_AGAIN) {
|
||||
c->read->handler = ngx_ssl_handshake_handler;
|
||||
c->write->handler = ngx_ssl_handshake_handler;
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
c->ssl->handshaked = 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
sslerr = SSL_get_error(c->ssl->connection, n);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_READ) {
|
||||
c->read->ready = 0;
|
||||
c->read->handler = ngx_ssl_handshake_handler;
|
||||
c->write->handler = ngx_ssl_handshake_handler;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
||||
c->write->ready = 0;
|
||||
c->read->handler = ngx_ssl_handshake_handler;
|
||||
c->write->handler = ngx_ssl_handshake_handler;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
|
||||
|
||||
c->ssl->no_wait_shutdown = 1;
|
||||
c->ssl->no_send_shutdown = 1;
|
||||
c->read->eof = 1;
|
||||
|
||||
if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
|
||||
ngx_connection_error(c, err,
|
||||
"peer closed connection in SSL handshake");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (c->ssl->handshake_rejected) {
|
||||
ngx_connection_error(c, err, "handshake rejected");
|
||||
ERR_clear_error();
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->read->error = 1;
|
||||
|
||||
ngx_ssl_connection_error(c, sslerr, err, "SSL_do_handshake() failed");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
#ifdef SSL_READ_EARLY_DATA_SUCCESS
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_try_early_data(ngx_connection_t *c)
|
||||
{
|
||||
int n, sslerr;
|
||||
u_char buf;
|
||||
size_t readbytes;
|
||||
ngx_err_t err;
|
||||
ngx_int_t rc;
|
||||
|
||||
ngx_ssl_clear_error(c->log);
|
||||
|
||||
readbytes = 0;
|
||||
|
||||
n = SSL_read_early_data(c->ssl->connection, &buf, 1, &readbytes);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_read_early_data: %d, %uz", n, readbytes);
|
||||
|
||||
if (n == SSL_READ_EARLY_DATA_FINISH) {
|
||||
c->ssl->try_early_data = 0;
|
||||
return ngx_ssl_handshake(c);
|
||||
}
|
||||
|
||||
if (n == SSL_READ_EARLY_DATA_SUCCESS) {
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
ngx_ssl_handshake_log(c);
|
||||
#endif
|
||||
|
||||
c->ssl->try_early_data = 0;
|
||||
|
||||
c->ssl->early_buf = buf;
|
||||
c->ssl->early_preread = 1;
|
||||
|
||||
c->ssl->in_early = 1;
|
||||
|
||||
c->recv = ngx_ssl_recv;
|
||||
c->send = ngx_ssl_write;
|
||||
c->recv_chain = ngx_ssl_recv_chain;
|
||||
c->send_chain = ngx_ssl_send_chain;
|
||||
|
||||
c->read->ready = 1;
|
||||
c->write->ready = 1;
|
||||
|
||||
#if (defined BIO_get_ktls_send && !NGX_WIN32)
|
||||
|
||||
if (BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)) == 1) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"BIO_get_ktls_send(): 1");
|
||||
c->ssl->sendfile = 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
rc = ngx_ssl_ocsp_validate(c);
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (rc == NGX_AGAIN) {
|
||||
c->read->handler = ngx_ssl_handshake_handler;
|
||||
c->write->handler = ngx_ssl_handshake_handler;
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
c->ssl->handshaked = 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* SSL_READ_EARLY_DATA_ERROR */
|
||||
|
||||
sslerr = SSL_get_error(c->ssl->connection, n);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_READ) {
|
||||
c->read->ready = 0;
|
||||
c->read->handler = ngx_ssl_handshake_handler;
|
||||
c->write->handler = ngx_ssl_handshake_handler;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
||||
c->write->ready = 0;
|
||||
c->read->handler = ngx_ssl_handshake_handler;
|
||||
c->write->handler = ngx_ssl_handshake_handler;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
|
||||
|
||||
c->ssl->no_wait_shutdown = 1;
|
||||
c->ssl->no_send_shutdown = 1;
|
||||
c->read->eof = 1;
|
||||
|
||||
if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
|
||||
ngx_connection_error(c, err,
|
||||
"peer closed connection in SSL handshake");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->read->error = 1;
|
||||
|
||||
ngx_ssl_connection_error(c, sslerr, err, "SSL_read_early_data() failed");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
|
||||
static void
|
||||
ngx_ssl_handshake_log(ngx_connection_t *c)
|
||||
{
|
||||
char buf[129], *s, *d;
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
|
||||
const
|
||||
#endif
|
||||
SSL_CIPHER *cipher;
|
||||
|
||||
if (!(c->log->log_level & NGX_LOG_DEBUG_EVENT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cipher = SSL_get_current_cipher(c->ssl->connection);
|
||||
|
||||
if (cipher) {
|
||||
SSL_CIPHER_description(cipher, &buf[1], 128);
|
||||
|
||||
for (s = &buf[1], d = buf; *s; s++) {
|
||||
if (*s == ' ' && *d == ' ') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*s == LF || *s == CR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*++d = *s;
|
||||
}
|
||||
|
||||
if (*d != ' ') {
|
||||
d++;
|
||||
}
|
||||
|
||||
*d = '\0';
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL: %s, cipher: \"%s\"",
|
||||
SSL_get_version(c->ssl->connection), &buf[1]);
|
||||
|
||||
if (SSL_session_reused(c->ssl->connection)) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL reused session");
|
||||
}
|
||||
|
||||
} else {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL no shared ciphers");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_handshake_handler(ngx_event_t *ev)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ev->data;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL handshake handler: %d", ev->write);
|
||||
|
||||
if (ev->timedout) {
|
||||
c->ssl->handler(c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ngx_ssl_handshake(c) == NGX_AGAIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
c->ssl->handler(c);
|
||||
}
|
||||
|
||||
|
||||
ssize_t
|
||||
ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit)
|
||||
{
|
||||
u_char *last;
|
||||
ssize_t n, bytes, size;
|
||||
ngx_buf_t *b;
|
||||
|
||||
bytes = 0;
|
||||
|
||||
b = cl->buf;
|
||||
last = b->last;
|
||||
|
||||
for ( ;; ) {
|
||||
size = b->end - last;
|
||||
|
||||
if (limit) {
|
||||
if (bytes >= limit) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
if (bytes + size > limit) {
|
||||
size = (ssize_t) (limit - bytes);
|
||||
}
|
||||
}
|
||||
|
||||
n = ngx_ssl_recv(c, last, size);
|
||||
|
||||
if (n > 0) {
|
||||
last += n;
|
||||
bytes += n;
|
||||
|
||||
if (!c->read->ready) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
if (last == b->end) {
|
||||
cl = cl->next;
|
||||
|
||||
if (cl == NULL) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
b = cl->buf;
|
||||
last = b->last;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bytes) {
|
||||
|
||||
if (n == 0 || n == NGX_ERROR) {
|
||||
c->read->ready = 1;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ssize_t
|
||||
ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size)
|
||||
{
|
||||
int n, bytes;
|
||||
|
||||
#ifdef SSL_READ_EARLY_DATA_SUCCESS
|
||||
if (c->ssl->in_early) {
|
||||
return ngx_ssl_recv_early(c, buf, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (c->ssl->last == NGX_ERROR) {
|
||||
c->read->error = 1;
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (c->ssl->last == NGX_DONE) {
|
||||
c->read->ready = 0;
|
||||
c->read->eof = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytes = 0;
|
||||
|
||||
ngx_ssl_clear_error(c->log);
|
||||
|
||||
/*
|
||||
* SSL_read() may return data in parts, so try to read
|
||||
* until SSL_read() would return no data
|
||||
*/
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
n = SSL_read(c->ssl->connection, buf, size);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_read: %d", n);
|
||||
|
||||
if (n > 0) {
|
||||
bytes += n;
|
||||
}
|
||||
|
||||
c->ssl->last = ngx_ssl_handle_recv(c, n);
|
||||
|
||||
if (c->ssl->last == NGX_OK) {
|
||||
|
||||
size -= n;
|
||||
|
||||
if (size == 0) {
|
||||
c->read->ready = 1;
|
||||
|
||||
if (c->read->available >= 0) {
|
||||
c->read->available -= bytes;
|
||||
|
||||
/*
|
||||
* there can be data buffered at SSL layer,
|
||||
* so we post an event to continue reading on the next
|
||||
* iteration of the event loop
|
||||
*/
|
||||
|
||||
if (c->read->available < 0) {
|
||||
c->read->available = 0;
|
||||
c->read->ready = 0;
|
||||
|
||||
if (c->read->posted) {
|
||||
ngx_delete_posted_event(c->read);
|
||||
}
|
||||
|
||||
ngx_post_event(c->read, &ngx_posted_next_events);
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_read: avail:%d", c->read->available);
|
||||
|
||||
} else {
|
||||
|
||||
#if (NGX_HAVE_FIONREAD)
|
||||
|
||||
if (ngx_socket_nread(c->fd, &c->read->available) == -1) {
|
||||
c->read->error = 1;
|
||||
ngx_connection_error(c, ngx_socket_errno,
|
||||
ngx_socket_nread_n " failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_read: avail:%d", c->read->available);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
buf += n;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bytes) {
|
||||
if (c->ssl->last != NGX_AGAIN) {
|
||||
c->read->ready = 1;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
switch (c->ssl->last) {
|
||||
|
||||
case NGX_DONE:
|
||||
c->read->ready = 0;
|
||||
c->read->eof = 1;
|
||||
return 0;
|
||||
|
||||
case NGX_ERROR:
|
||||
c->read->error = 1;
|
||||
|
||||
/* fall through */
|
||||
|
||||
case NGX_AGAIN:
|
||||
return c->ssl->last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef SSL_READ_EARLY_DATA_SUCCESS
|
||||
|
||||
static ssize_t
|
||||
ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, size_t size)
|
||||
{
|
||||
int n, bytes;
|
||||
size_t readbytes;
|
||||
|
||||
if (c->ssl->last == NGX_ERROR) {
|
||||
c->read->error = 1;
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (c->ssl->last == NGX_DONE) {
|
||||
c->read->ready = 0;
|
||||
c->read->eof = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytes = 0;
|
||||
|
||||
ngx_ssl_clear_error(c->log);
|
||||
|
||||
if (c->ssl->early_preread) {
|
||||
|
||||
if (size == 0) {
|
||||
c->read->ready = 0;
|
||||
c->read->eof = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*buf = c->ssl->early_buf;
|
||||
|
||||
c->ssl->early_preread = 0;
|
||||
|
||||
bytes = 1;
|
||||
size -= 1;
|
||||
buf += 1;
|
||||
}
|
||||
|
||||
if (c->ssl->write_blocked) {
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSL_read_early_data() may return data in parts, so try to read
|
||||
* until SSL_read_early_data() would return no data
|
||||
*/
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
readbytes = 0;
|
||||
|
||||
n = SSL_read_early_data(c->ssl->connection, buf, size, &readbytes);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_read_early_data: %d, %uz", n, readbytes);
|
||||
|
||||
if (n == SSL_READ_EARLY_DATA_SUCCESS) {
|
||||
|
||||
c->ssl->last = ngx_ssl_handle_recv(c, 1);
|
||||
|
||||
bytes += readbytes;
|
||||
size -= readbytes;
|
||||
|
||||
if (size == 0) {
|
||||
c->read->ready = 1;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
buf += readbytes;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (n == SSL_READ_EARLY_DATA_FINISH) {
|
||||
|
||||
c->ssl->last = ngx_ssl_handle_recv(c, 1);
|
||||
c->ssl->in_early = 0;
|
||||
|
||||
if (bytes) {
|
||||
c->read->ready = 1;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
return ngx_ssl_recv(c, buf, size);
|
||||
}
|
||||
|
||||
/* SSL_READ_EARLY_DATA_ERROR */
|
||||
|
||||
c->ssl->last = ngx_ssl_handle_recv(c, 0);
|
||||
|
||||
if (bytes) {
|
||||
if (c->ssl->last != NGX_AGAIN) {
|
||||
c->read->ready = 1;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
switch (c->ssl->last) {
|
||||
|
||||
case NGX_DONE:
|
||||
c->read->ready = 0;
|
||||
c->read->eof = 1;
|
||||
return 0;
|
||||
|
||||
case NGX_ERROR:
|
||||
c->read->error = 1;
|
||||
|
||||
/* fall through */
|
||||
|
||||
case NGX_AGAIN:
|
||||
return c->ssl->last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_handle_recv(ngx_connection_t *c, int n)
|
||||
{
|
||||
int sslerr;
|
||||
ngx_err_t err;
|
||||
|
||||
#ifndef SSL_OP_NO_RENEGOTIATION
|
||||
|
||||
if (c->ssl->renegotiation) {
|
||||
/*
|
||||
* disable renegotiation (CVE-2009-3555):
|
||||
* OpenSSL (at least up to 0.9.8l) does not handle disabled
|
||||
* renegotiation gracefully, so drop connection here
|
||||
*/
|
||||
|
||||
ngx_log_error(NGX_LOG_NOTICE, c->log, 0, "SSL renegotiation disabled");
|
||||
|
||||
while (ERR_peek_error()) {
|
||||
ngx_ssl_error(NGX_LOG_DEBUG, c->log, 0,
|
||||
"ignoring stale global SSL error");
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
|
||||
c->ssl->no_wait_shutdown = 1;
|
||||
c->ssl->no_send_shutdown = 1;
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (n > 0) {
|
||||
|
||||
if (c->ssl->saved_write_handler) {
|
||||
|
||||
c->write->handler = c->ssl->saved_write_handler;
|
||||
c->ssl->saved_write_handler = NULL;
|
||||
c->write->ready = 1;
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_post_event(c->write, &ngx_posted_events);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
sslerr = SSL_get_error(c->ssl->connection, n);
|
||||
|
||||
err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_READ) {
|
||||
|
||||
if (c->ssl->saved_write_handler) {
|
||||
|
||||
c->write->handler = c->ssl->saved_write_handler;
|
||||
c->ssl->saved_write_handler = NULL;
|
||||
c->write->ready = 1;
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_post_event(c->write, &ngx_posted_events);
|
||||
}
|
||||
|
||||
c->read->ready = 0;
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_read: want write");
|
||||
|
||||
c->write->ready = 0;
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* we do not set the timer because there is already the read event timer
|
||||
*/
|
||||
|
||||
if (c->ssl->saved_write_handler == NULL) {
|
||||
c->ssl->saved_write_handler = c->write->handler;
|
||||
c->write->handler = ngx_ssl_write_handler;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
c->ssl->no_wait_shutdown = 1;
|
||||
c->ssl->no_send_shutdown = 1;
|
||||
|
||||
if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"peer shutdown SSL cleanly");
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
ngx_ssl_connection_error(c, sslerr, err, "SSL_read() failed");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_write_handler(ngx_event_t *wev)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = wev->data;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL write handler");
|
||||
|
||||
c->read->handler(c->read);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* OpenSSL has no SSL_writev() so we copy several bufs into our 16K buffer
|
||||
* before the SSL_write() call to decrease a SSL overhead.
|
||||
*
|
||||
* Besides for protocols such as HTTP it is possible to always buffer
|
||||
* the output to decrease a SSL overhead some more.
|
||||
*/
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
|
||||
{
|
||||
int n;
|
||||
ngx_uint_t flush;
|
||||
ssize_t send, size, file_size;
|
||||
ngx_buf_t *buf;
|
||||
ngx_chain_t *cl;
|
||||
|
||||
if (!c->ssl->buffer) {
|
||||
|
||||
while (in) {
|
||||
if (ngx_buf_special(in->buf)) {
|
||||
in = in->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
n = ngx_ssl_write(c, in->buf->pos, in->buf->last - in->buf->pos);
|
||||
|
||||
if (n == NGX_ERROR) {
|
||||
return NGX_CHAIN_ERROR;
|
||||
}
|
||||
|
||||
if (n == NGX_AGAIN) {
|
||||
return in;
|
||||
}
|
||||
|
||||
in->buf->pos += n;
|
||||
|
||||
if (in->buf->pos == in->buf->last) {
|
||||
in = in->next;
|
||||
}
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
|
||||
/* the maximum limit size is the maximum int32_t value - the page size */
|
||||
|
||||
if (limit == 0 || limit > (off_t) (NGX_MAX_INT32_VALUE - ngx_pagesize)) {
|
||||
limit = NGX_MAX_INT32_VALUE - ngx_pagesize;
|
||||
}
|
||||
|
||||
buf = c->ssl->buf;
|
||||
|
||||
if (buf == NULL) {
|
||||
buf = ngx_create_temp_buf(c->pool, c->ssl->buffer_size);
|
||||
if (buf == NULL) {
|
||||
return NGX_CHAIN_ERROR;
|
||||
}
|
||||
|
||||
c->ssl->buf = buf;
|
||||
}
|
||||
|
||||
if (buf->start == NULL) {
|
||||
buf->start = ngx_palloc(c->pool, c->ssl->buffer_size);
|
||||
if (buf->start == NULL) {
|
||||
return NGX_CHAIN_ERROR;
|
||||
}
|
||||
|
||||
buf->pos = buf->start;
|
||||
buf->last = buf->start;
|
||||
buf->end = buf->start + c->ssl->buffer_size;
|
||||
}
|
||||
|
||||
send = buf->last - buf->pos;
|
||||
flush = (in == NULL) ? 1 : buf->flush;
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
while (in && buf->last < buf->end && send < limit) {
|
||||
if (in->buf->last_buf || in->buf->flush) {
|
||||
flush = 1;
|
||||
}
|
||||
|
||||
if (ngx_buf_special(in->buf)) {
|
||||
in = in->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in->buf->in_file && c->ssl->sendfile) {
|
||||
flush = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
size = in->buf->last - in->buf->pos;
|
||||
|
||||
if (size > buf->end - buf->last) {
|
||||
size = buf->end - buf->last;
|
||||
}
|
||||
|
||||
if (send + size > limit) {
|
||||
size = (ssize_t) (limit - send);
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL buf copy: %z", size);
|
||||
|
||||
ngx_memcpy(buf->last, in->buf->pos, size);
|
||||
|
||||
buf->last += size;
|
||||
in->buf->pos += size;
|
||||
send += size;
|
||||
|
||||
if (in->buf->pos == in->buf->last) {
|
||||
in = in->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flush && send < limit && buf->last < buf->end) {
|
||||
break;
|
||||
}
|
||||
|
||||
size = buf->last - buf->pos;
|
||||
|
||||
if (size == 0) {
|
||||
|
||||
if (in && in->buf->in_file && send < limit) {
|
||||
|
||||
/* coalesce the neighbouring file bufs */
|
||||
|
||||
cl = in;
|
||||
file_size = (size_t) ngx_chain_coalesce_file(&cl, limit - send);
|
||||
|
||||
n = ngx_ssl_sendfile(c, in->buf, file_size);
|
||||
|
||||
if (n == NGX_ERROR) {
|
||||
return NGX_CHAIN_ERROR;
|
||||
}
|
||||
|
||||
if (n == NGX_AGAIN) {
|
||||
break;
|
||||
}
|
||||
|
||||
in = ngx_chain_update_sent(in, n);
|
||||
|
||||
send += n;
|
||||
flush = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
buf->flush = 0;
|
||||
c->buffered &= ~NGX_SSL_BUFFERED;
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
n = ngx_ssl_write(c, buf->pos, size);
|
||||
|
||||
if (n == NGX_ERROR) {
|
||||
return NGX_CHAIN_ERROR;
|
||||
}
|
||||
|
||||
if (n == NGX_AGAIN) {
|
||||
break;
|
||||
}
|
||||
|
||||
buf->pos += n;
|
||||
|
||||
if (n < size) {
|
||||
break;
|
||||
}
|
||||
|
||||
flush = 0;
|
||||
|
||||
buf->pos = buf->start;
|
||||
buf->last = buf->start;
|
||||
|
||||
if (in == NULL || send >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buf->flush = flush;
|
||||
|
||||
if (buf->pos < buf->last) {
|
||||
c->buffered |= NGX_SSL_BUFFERED;
|
||||
|
||||
} else {
|
||||
c->buffered &= ~NGX_SSL_BUFFERED;
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
|
||||
ssize_t
|
||||
ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size)
|
||||
{
|
||||
int n, sslerr;
|
||||
ngx_err_t err;
|
||||
|
||||
#ifdef SSL_READ_EARLY_DATA_SUCCESS
|
||||
if (c->ssl->in_early) {
|
||||
return ngx_ssl_write_early(c, data, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
ngx_ssl_clear_error(c->log);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size);
|
||||
|
||||
n = SSL_write(c->ssl->connection, data, size);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_write: %d", n);
|
||||
|
||||
if (n > 0) {
|
||||
|
||||
if (c->ssl->saved_read_handler) {
|
||||
|
||||
c->read->handler = c->ssl->saved_read_handler;
|
||||
c->ssl->saved_read_handler = NULL;
|
||||
c->read->ready = 1;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_post_event(c->read, &ngx_posted_events);
|
||||
}
|
||||
|
||||
c->sent += n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
sslerr = SSL_get_error(c->ssl->connection, n);
|
||||
|
||||
if (sslerr == SSL_ERROR_ZERO_RETURN) {
|
||||
|
||||
/*
|
||||
* OpenSSL 1.1.1 fails to return SSL_ERROR_SYSCALL if an error
|
||||
* happens during SSL_write() after close_notify alert from the
|
||||
* peer, and returns SSL_ERROR_ZERO_RETURN instead,
|
||||
* https://git.openssl.org/?p=openssl.git;a=commitdiff;h=8051ab2
|
||||
*/
|
||||
|
||||
sslerr = SSL_ERROR_SYSCALL;
|
||||
}
|
||||
|
||||
err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
||||
|
||||
if (c->ssl->saved_read_handler) {
|
||||
|
||||
c->read->handler = c->ssl->saved_read_handler;
|
||||
c->ssl->saved_read_handler = NULL;
|
||||
c->read->ready = 1;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_post_event(c->read, &ngx_posted_events);
|
||||
}
|
||||
|
||||
c->write->ready = 0;
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_READ) {
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_write: want read");
|
||||
|
||||
c->read->ready = 0;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* we do not set the timer because there is already
|
||||
* the write event timer
|
||||
*/
|
||||
|
||||
if (c->ssl->saved_read_handler == NULL) {
|
||||
c->ssl->saved_read_handler = c->read->handler;
|
||||
c->read->handler = ngx_ssl_read_handler;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
c->ssl->no_wait_shutdown = 1;
|
||||
c->ssl->no_send_shutdown = 1;
|
||||
c->write->error = 1;
|
||||
|
||||
ngx_ssl_connection_error(c, sslerr, err, "SSL_write() failed");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
#ifdef SSL_READ_EARLY_DATA_SUCCESS
|
||||
|
||||
static ssize_t
|
||||
ngx_ssl_write_early(ngx_connection_t *c, u_char *data, size_t size)
|
||||
{
|
||||
int n, sslerr;
|
||||
size_t written;
|
||||
ngx_err_t err;
|
||||
|
||||
ngx_ssl_clear_error(c->log);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size);
|
||||
|
||||
written = 0;
|
||||
|
||||
n = SSL_write_early_data(c->ssl->connection, data, size, &written);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_write_early_data: %d, %uz", n, written);
|
||||
|
||||
if (n > 0) {
|
||||
|
||||
if (c->ssl->saved_read_handler) {
|
||||
|
||||
c->read->handler = c->ssl->saved_read_handler;
|
||||
c->ssl->saved_read_handler = NULL;
|
||||
c->read->ready = 1;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_post_event(c->read, &ngx_posted_events);
|
||||
}
|
||||
|
||||
if (c->ssl->write_blocked) {
|
||||
c->ssl->write_blocked = 0;
|
||||
ngx_post_event(c->read, &ngx_posted_events);
|
||||
}
|
||||
|
||||
c->sent += written;
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
sslerr = SSL_get_error(c->ssl->connection, n);
|
||||
|
||||
err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_write_early_data: want write");
|
||||
|
||||
if (c->ssl->saved_read_handler) {
|
||||
|
||||
c->read->handler = c->ssl->saved_read_handler;
|
||||
c->ssl->saved_read_handler = NULL;
|
||||
c->read->ready = 1;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_post_event(c->read, &ngx_posted_events);
|
||||
}
|
||||
|
||||
/*
|
||||
* OpenSSL 1.1.1a fails to handle SSL_read_early_data()
|
||||
* if an SSL_write_early_data() call blocked on writing,
|
||||
* see https://github.com/openssl/openssl/issues/7757
|
||||
*/
|
||||
|
||||
c->ssl->write_blocked = 1;
|
||||
|
||||
c->write->ready = 0;
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_READ) {
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_write_early_data: want read");
|
||||
|
||||
c->read->ready = 0;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* we do not set the timer because there is already
|
||||
* the write event timer
|
||||
*/
|
||||
|
||||
if (c->ssl->saved_read_handler == NULL) {
|
||||
c->ssl->saved_read_handler = c->read->handler;
|
||||
c->read->handler = ngx_ssl_read_handler;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
c->ssl->no_wait_shutdown = 1;
|
||||
c->ssl->no_send_shutdown = 1;
|
||||
c->write->error = 1;
|
||||
|
||||
ngx_ssl_connection_error(c, sslerr, err, "SSL_write_early_data() failed");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static ssize_t
|
||||
ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size)
|
||||
{
|
||||
#if (defined BIO_get_ktls_send && !NGX_WIN32)
|
||||
|
||||
int sslerr, flags;
|
||||
ssize_t n;
|
||||
ngx_err_t err;
|
||||
|
||||
ngx_ssl_clear_error(c->log);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL to sendfile: @%O %uz",
|
||||
file->file_pos, size);
|
||||
|
||||
ngx_set_errno(0);
|
||||
|
||||
#if (NGX_HAVE_SENDFILE_NODISKIO)
|
||||
|
||||
flags = (c->busy_count <= 2) ? SF_NODISKIO : 0;
|
||||
|
||||
if (file->file->directio) {
|
||||
flags |= SF_NOCACHE;
|
||||
}
|
||||
|
||||
#else
|
||||
flags = 0;
|
||||
#endif
|
||||
|
||||
n = SSL_sendfile(c->ssl->connection, file->file->fd, file->file_pos,
|
||||
size, flags);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_sendfile: %d", n);
|
||||
|
||||
if (n > 0) {
|
||||
|
||||
if (c->ssl->saved_read_handler) {
|
||||
|
||||
c->read->handler = c->ssl->saved_read_handler;
|
||||
c->ssl->saved_read_handler = NULL;
|
||||
c->read->ready = 1;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_post_event(c->read, &ngx_posted_events);
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_SENDFILE_NODISKIO)
|
||||
c->busy_count = 0;
|
||||
#endif
|
||||
|
||||
c->sent += n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
|
||||
/*
|
||||
* if sendfile returns zero, then someone has truncated the file,
|
||||
* so the offset became beyond the end of the file
|
||||
*/
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
|
||||
"SSL_sendfile() reported that \"%s\" was truncated at %O",
|
||||
file->file->name.data, file->file_pos);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
sslerr = SSL_get_error(c->ssl->connection, n);
|
||||
|
||||
if (sslerr == SSL_ERROR_ZERO_RETURN) {
|
||||
|
||||
/*
|
||||
* OpenSSL fails to return SSL_ERROR_SYSCALL if an error
|
||||
* happens during writing after close_notify alert from the
|
||||
* peer, and returns SSL_ERROR_ZERO_RETURN instead
|
||||
*/
|
||||
|
||||
sslerr = SSL_ERROR_SYSCALL;
|
||||
}
|
||||
|
||||
if (sslerr == SSL_ERROR_SSL
|
||||
&& ERR_GET_REASON(ERR_peek_error()) == SSL_R_UNINITIALIZED
|
||||
&& ngx_errno != 0)
|
||||
{
|
||||
/*
|
||||
* OpenSSL fails to return SSL_ERROR_SYSCALL if an error
|
||||
* happens in sendfile(), and returns SSL_ERROR_SSL with
|
||||
* SSL_R_UNINITIALIZED reason instead
|
||||
*/
|
||||
|
||||
sslerr = SSL_ERROR_SYSCALL;
|
||||
}
|
||||
|
||||
err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
||||
|
||||
if (c->ssl->saved_read_handler) {
|
||||
|
||||
c->read->handler = c->ssl->saved_read_handler;
|
||||
c->ssl->saved_read_handler = NULL;
|
||||
c->read->ready = 1;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_post_event(c->read, &ngx_posted_events);
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_SENDFILE_NODISKIO)
|
||||
|
||||
if (ngx_errno == EBUSY) {
|
||||
c->busy_count++;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_sendfile() busy, count:%d", c->busy_count);
|
||||
|
||||
if (c->write->posted) {
|
||||
ngx_delete_posted_event(c->write);
|
||||
}
|
||||
|
||||
ngx_post_event(c->write, &ngx_posted_next_events);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
c->write->ready = 0;
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_READ) {
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_sendfile: want read");
|
||||
|
||||
c->read->ready = 0;
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* we do not set the timer because there is already
|
||||
* the write event timer
|
||||
*/
|
||||
|
||||
if (c->ssl->saved_read_handler == NULL) {
|
||||
c->ssl->saved_read_handler = c->read->handler;
|
||||
c->read->handler = ngx_ssl_read_handler;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
c->ssl->no_wait_shutdown = 1;
|
||||
c->ssl->no_send_shutdown = 1;
|
||||
c->write->error = 1;
|
||||
|
||||
ngx_ssl_connection_error(c, sslerr, err, "SSL_sendfile() failed");
|
||||
|
||||
#else
|
||||
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
|
||||
"SSL_sendfile() not available");
|
||||
#endif
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_read_handler(ngx_event_t *rev)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = rev->data;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL read handler");
|
||||
|
||||
c->write->handler(c->write);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_ssl_free_buffer(ngx_connection_t *c)
|
||||
{
|
||||
if (c->ssl->buf && c->ssl->buf->start) {
|
||||
if (ngx_pfree(c->pool, c->ssl->buf->start) == NGX_OK) {
|
||||
c->ssl->buf->start = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_shutdown(ngx_connection_t *c)
|
||||
{
|
||||
int n, sslerr, mode;
|
||||
ngx_int_t rc;
|
||||
ngx_err_t err;
|
||||
ngx_uint_t tries;
|
||||
|
||||
rc = NGX_OK;
|
||||
|
||||
ngx_ssl_ocsp_cleanup(c);
|
||||
|
||||
if (SSL_in_init(c->ssl->connection)) {
|
||||
/*
|
||||
* OpenSSL 1.0.2f complains if SSL_shutdown() is called during
|
||||
* an SSL handshake, while previous versions always return 0.
|
||||
* Avoid calling SSL_shutdown() if handshake wasn't completed.
|
||||
*/
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (c->timedout || c->error || c->buffered) {
|
||||
mode = SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN;
|
||||
SSL_set_quiet_shutdown(c->ssl->connection, 1);
|
||||
|
||||
} else {
|
||||
mode = SSL_get_shutdown(c->ssl->connection);
|
||||
|
||||
if (c->ssl->no_wait_shutdown) {
|
||||
mode |= SSL_RECEIVED_SHUTDOWN;
|
||||
}
|
||||
|
||||
if (c->ssl->no_send_shutdown) {
|
||||
mode |= SSL_SENT_SHUTDOWN;
|
||||
}
|
||||
|
||||
if (c->ssl->no_wait_shutdown && c->ssl->no_send_shutdown) {
|
||||
SSL_set_quiet_shutdown(c->ssl->connection, 1);
|
||||
}
|
||||
}
|
||||
|
||||
SSL_set_shutdown(c->ssl->connection, mode);
|
||||
|
||||
ngx_ssl_clear_error(c->log);
|
||||
|
||||
tries = 2;
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
/*
|
||||
* For bidirectional shutdown, SSL_shutdown() needs to be called
|
||||
* twice: first call sends the "close notify" alert and returns 0,
|
||||
* second call waits for the peer's "close notify" alert.
|
||||
*/
|
||||
|
||||
n = SSL_shutdown(c->ssl->connection);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n);
|
||||
|
||||
if (n == 1) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (n == 0 && tries-- > 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* before 0.9.8m SSL_shutdown() returned 0 instead of -1 on errors */
|
||||
|
||||
sslerr = SSL_get_error(c->ssl->connection, n);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL_get_error: %d", sslerr);
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) {
|
||||
c->read->handler = ngx_ssl_shutdown_handler;
|
||||
c->write->handler = ngx_ssl_shutdown_handler;
|
||||
|
||||
if (sslerr == SSL_ERROR_WANT_READ) {
|
||||
c->read->ready = 0;
|
||||
|
||||
} else {
|
||||
c->write->ready = 0;
|
||||
}
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
ngx_add_timer(c->read, 3000);
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
|
||||
|
||||
ngx_ssl_connection_error(c, sslerr, err, "SSL_shutdown() failed");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
failed:
|
||||
|
||||
rc = NGX_ERROR;
|
||||
|
||||
done:
|
||||
|
||||
if (c->ssl->shutdown_without_free) {
|
||||
c->ssl->shutdown_without_free = 0;
|
||||
c->recv = ngx_recv;
|
||||
return rc;
|
||||
}
|
||||
|
||||
SSL_free(c->ssl->connection);
|
||||
c->ssl = NULL;
|
||||
c->recv = ngx_recv;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_shutdown_handler(ngx_event_t *ev)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
ngx_connection_handler_pt handler;
|
||||
|
||||
c = ev->data;
|
||||
handler = c->ssl->handler;
|
||||
|
||||
if (ev->timedout) {
|
||||
c->timedout = 1;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "SSL shutdown handler");
|
||||
|
||||
if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler(c);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err,
|
||||
char *text)
|
||||
{
|
||||
int n;
|
||||
ngx_uint_t level;
|
||||
|
||||
level = NGX_LOG_CRIT;
|
||||
|
||||
if (sslerr == SSL_ERROR_SYSCALL) {
|
||||
|
||||
if (err == NGX_ECONNRESET
|
||||
#if (NGX_WIN32)
|
||||
|| err == NGX_ECONNABORTED
|
||||
#endif
|
||||
|| err == NGX_EPIPE
|
||||
|| err == NGX_ENOTCONN
|
||||
|| err == NGX_ETIMEDOUT
|
||||
|| err == NGX_ECONNREFUSED
|
||||
|| err == NGX_ENETDOWN
|
||||
|| err == NGX_ENETUNREACH
|
||||
|| err == NGX_EHOSTDOWN
|
||||
|| err == NGX_EHOSTUNREACH)
|
||||
{
|
||||
switch (c->log_error) {
|
||||
|
||||
case NGX_ERROR_IGNORE_ECONNRESET:
|
||||
case NGX_ERROR_INFO:
|
||||
level = NGX_LOG_INFO;
|
||||
break;
|
||||
|
||||
case NGX_ERROR_ERR:
|
||||
level = NGX_LOG_ERR;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (sslerr == SSL_ERROR_SSL) {
|
||||
|
||||
n = ERR_GET_REASON(ERR_peek_error());
|
||||
|
||||
/* handshake failures */
|
||||
if (n == SSL_R_BAD_CHANGE_CIPHER_SPEC /* 103 */
|
||||
#ifdef SSL_R_NO_SUITABLE_KEY_SHARE
|
||||
|| n == SSL_R_NO_SUITABLE_KEY_SHARE /* 101 */
|
||||
#endif
|
||||
#ifdef SSL_R_BAD_KEY_SHARE
|
||||
|| n == SSL_R_BAD_KEY_SHARE /* 108 */
|
||||
#endif
|
||||
#ifdef SSL_R_BAD_EXTENSION
|
||||
|| n == SSL_R_BAD_EXTENSION /* 110 */
|
||||
#endif
|
||||
#ifdef SSL_R_NO_SUITABLE_SIGNATURE_ALGORITHM
|
||||
|| n == SSL_R_NO_SUITABLE_SIGNATURE_ALGORITHM /* 118 */
|
||||
#endif
|
||||
|| n == SSL_R_BLOCK_CIPHER_PAD_IS_WRONG /* 129 */
|
||||
|| n == SSL_R_DIGEST_CHECK_FAILED /* 149 */
|
||||
|| n == SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST /* 151 */
|
||||
|| n == SSL_R_EXCESSIVE_MESSAGE_SIZE /* 152 */
|
||||
|| n == SSL_R_HTTPS_PROXY_REQUEST /* 155 */
|
||||
|| n == SSL_R_HTTP_REQUEST /* 156 */
|
||||
|| n == SSL_R_LENGTH_MISMATCH /* 159 */
|
||||
#ifdef SSL_R_NO_CIPHERS_PASSED
|
||||
|| n == SSL_R_NO_CIPHERS_PASSED /* 182 */
|
||||
#endif
|
||||
|| n == SSL_R_NO_CIPHERS_SPECIFIED /* 183 */
|
||||
#ifdef SSL_R_BAD_CIPHER
|
||||
|| n == SSL_R_BAD_CIPHER /* 186 */
|
||||
#endif
|
||||
|| n == SSL_R_NO_COMPRESSION_SPECIFIED /* 187 */
|
||||
|| n == SSL_R_NO_SHARED_CIPHER /* 193 */
|
||||
|| n == SSL_R_RECORD_LENGTH_MISMATCH /* 213 */
|
||||
#ifdef SSL_R_CLIENTHELLO_TLSEXT
|
||||
|| n == SSL_R_CLIENTHELLO_TLSEXT /* 226 */
|
||||
#endif
|
||||
#ifdef SSL_R_PARSE_TLSEXT
|
||||
|| n == SSL_R_PARSE_TLSEXT /* 227 */
|
||||
#endif
|
||||
#ifdef SSL_R_CALLBACK_FAILED
|
||||
|| n == SSL_R_CALLBACK_FAILED /* 234 */
|
||||
#endif
|
||||
#ifdef SSL_R_NO_APPLICATION_PROTOCOL
|
||||
|| n == SSL_R_NO_APPLICATION_PROTOCOL /* 235 */
|
||||
#endif
|
||||
|| n == SSL_R_UNEXPECTED_MESSAGE /* 244 */
|
||||
|| n == SSL_R_UNEXPECTED_RECORD /* 245 */
|
||||
|| n == SSL_R_UNKNOWN_ALERT_TYPE /* 246 */
|
||||
|| n == SSL_R_UNKNOWN_PROTOCOL /* 252 */
|
||||
#ifdef SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS
|
||||
|| n == SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS /* 253 */
|
||||
#endif
|
||||
|| n == SSL_R_UNSUPPORTED_PROTOCOL /* 258 */
|
||||
#ifdef SSL_R_NO_SHARED_GROUP
|
||||
|| n == SSL_R_NO_SHARED_GROUP /* 266 */
|
||||
#endif
|
||||
|| n == SSL_R_WRONG_VERSION_NUMBER /* 267 */
|
||||
|| n == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC /* 281 */
|
||||
#ifdef SSL_R_APPLICATION_DATA_AFTER_CLOSE_NOTIFY
|
||||
|| n == SSL_R_APPLICATION_DATA_AFTER_CLOSE_NOTIFY /* 291 */
|
||||
#endif
|
||||
#ifdef SSL_R_APPLICATION_DATA_ON_SHUTDOWN
|
||||
|| n == SSL_R_APPLICATION_DATA_ON_SHUTDOWN /* 291 */
|
||||
#endif
|
||||
#ifdef SSL_R_BAD_ECPOINT
|
||||
|| n == SSL_R_BAD_ECPOINT /* 306 */
|
||||
#endif
|
||||
#ifdef SSL_R_RENEGOTIATE_EXT_TOO_LONG
|
||||
|| n == SSL_R_RENEGOTIATE_EXT_TOO_LONG /* 335 */
|
||||
|| n == SSL_R_RENEGOTIATION_ENCODING_ERR /* 336 */
|
||||
|| n == SSL_R_RENEGOTIATION_MISMATCH /* 337 */
|
||||
#endif
|
||||
#ifdef SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED
|
||||
|| n == SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED /* 338 */
|
||||
#endif
|
||||
#ifdef SSL_R_SCSV_RECEIVED_WHEN_RENEGOTIATING
|
||||
|| n == SSL_R_SCSV_RECEIVED_WHEN_RENEGOTIATING /* 345 */
|
||||
#endif
|
||||
#ifdef SSL_R_INAPPROPRIATE_FALLBACK
|
||||
|| n == SSL_R_INAPPROPRIATE_FALLBACK /* 373 */
|
||||
#endif
|
||||
#ifdef SSL_R_CERT_CB_ERROR
|
||||
|| n == SSL_R_CERT_CB_ERROR /* 377 */
|
||||
#endif
|
||||
#ifdef SSL_R_VERSION_TOO_LOW
|
||||
|| n == SSL_R_VERSION_TOO_LOW /* 396 */
|
||||
#endif
|
||||
#ifdef SSL_R_BAD_RECORD_TYPE
|
||||
|| n == SSL_R_BAD_RECORD_TYPE /* 443 */
|
||||
#endif
|
||||
|| n == 1000 /* SSL_R_SSLV3_ALERT_CLOSE_NOTIFY */
|
||||
#ifdef SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE
|
||||
|| n == SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE /* 1010 */
|
||||
|| n == SSL_R_SSLV3_ALERT_BAD_RECORD_MAC /* 1020 */
|
||||
|| n == SSL_R_TLSV1_ALERT_DECRYPTION_FAILED /* 1021 */
|
||||
|| n == SSL_R_TLSV1_ALERT_RECORD_OVERFLOW /* 1022 */
|
||||
|| n == SSL_R_SSLV3_ALERT_DECOMPRESSION_FAILURE /* 1030 */
|
||||
|| n == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE /* 1040 */
|
||||
|| n == SSL_R_SSLV3_ALERT_NO_CERTIFICATE /* 1041 */
|
||||
|| n == SSL_R_SSLV3_ALERT_BAD_CERTIFICATE /* 1042 */
|
||||
|| n == SSL_R_SSLV3_ALERT_UNSUPPORTED_CERTIFICATE /* 1043 */
|
||||
|| n == SSL_R_SSLV3_ALERT_CERTIFICATE_REVOKED /* 1044 */
|
||||
|| n == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED /* 1045 */
|
||||
|| n == SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN /* 1046 */
|
||||
|| n == SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER /* 1047 */
|
||||
|| n == SSL_R_TLSV1_ALERT_UNKNOWN_CA /* 1048 */
|
||||
|| n == SSL_R_TLSV1_ALERT_ACCESS_DENIED /* 1049 */
|
||||
|| n == SSL_R_TLSV1_ALERT_DECODE_ERROR /* 1050 */
|
||||
|| n == SSL_R_TLSV1_ALERT_DECRYPT_ERROR /* 1051 */
|
||||
|| n == SSL_R_TLSV1_ALERT_EXPORT_RESTRICTION /* 1060 */
|
||||
|| n == SSL_R_TLSV1_ALERT_PROTOCOL_VERSION /* 1070 */
|
||||
|| n == SSL_R_TLSV1_ALERT_INSUFFICIENT_SECURITY /* 1071 */
|
||||
|| n == SSL_R_TLSV1_ALERT_INTERNAL_ERROR /* 1080 */
|
||||
|| n == SSL_R_TLSV1_ALERT_USER_CANCELLED /* 1090 */
|
||||
|| n == SSL_R_TLSV1_ALERT_NO_RENEGOTIATION /* 1100 */
|
||||
#endif
|
||||
)
|
||||
{
|
||||
switch (c->log_error) {
|
||||
|
||||
case NGX_ERROR_IGNORE_ECONNRESET:
|
||||
case NGX_ERROR_INFO:
|
||||
level = NGX_LOG_INFO;
|
||||
break;
|
||||
|
||||
case NGX_ERROR_ERR:
|
||||
level = NGX_LOG_ERR;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngx_ssl_error(level, c->log, err, text);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_clear_error(ngx_log_t *log)
|
||||
{
|
||||
while (ERR_peek_error()) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0, "ignoring stale global SSL error");
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
}
|
||||
|
||||
|
||||
void ngx_cdecl
|
||||
ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, char *fmt, ...)
|
||||
{
|
||||
int flags;
|
||||
u_long n;
|
||||
va_list args;
|
||||
u_char *p, *last;
|
||||
u_char errstr[NGX_MAX_CONF_ERRSTR];
|
||||
const char *data;
|
||||
|
||||
last = errstr + NGX_MAX_CONF_ERRSTR;
|
||||
|
||||
va_start(args, fmt);
|
||||
p = ngx_vslprintf(errstr, last - 1, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (ERR_peek_error()) {
|
||||
p = ngx_cpystrn(p, (u_char *) " (SSL:", last - p);
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
n = ERR_peek_error_data(&data, &flags);
|
||||
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* ERR_error_string_n() requires at least one byte */
|
||||
|
||||
if (p >= last - 1) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
*p++ = ' ';
|
||||
|
||||
ERR_error_string_n(n, (char *) p, last - p);
|
||||
|
||||
while (p < last && *p) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p < last && *data && (flags & ERR_TXT_STRING)) {
|
||||
*p++ = ':';
|
||||
p = ngx_cpystrn(p, (u_char *) data, last - p);
|
||||
}
|
||||
|
||||
next:
|
||||
|
||||
(void) ERR_get_error();
|
||||
}
|
||||
|
||||
if (p < last) {
|
||||
*p++ = ')';
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_error(level, log, err, "%*s", p - errstr, errstr);
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
|
||||
ngx_array_t *certificates, ssize_t builtin_session_cache,
|
||||
ngx_shm_zone_t *shm_zone, time_t timeout)
|
||||
{
|
||||
long cache_mode;
|
||||
|
||||
SSL_CTX_set_timeout(ssl->ctx, (long) timeout);
|
||||
|
||||
if (ngx_ssl_session_id_context(ssl, sess_ctx, certificates) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (builtin_session_cache == NGX_SSL_NO_SCACHE) {
|
||||
SSL_CTX_set_session_cache_mode(ssl->ctx, SSL_SESS_CACHE_OFF);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (builtin_session_cache == NGX_SSL_NONE_SCACHE) {
|
||||
|
||||
/*
|
||||
* If the server explicitly says that it does not support
|
||||
* session reuse (see SSL_SESS_CACHE_OFF above), then
|
||||
* Outlook Express fails to upload a sent email to
|
||||
* the Sent Items folder on the IMAP server via a separate IMAP
|
||||
* connection in the background. Therefore we have a special
|
||||
* mode (SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL_STORE)
|
||||
* where the server pretends that it supports session reuse,
|
||||
* but it does not actually store any session.
|
||||
*/
|
||||
|
||||
SSL_CTX_set_session_cache_mode(ssl->ctx,
|
||||
SSL_SESS_CACHE_SERVER
|
||||
|SSL_SESS_CACHE_NO_AUTO_CLEAR
|
||||
|SSL_SESS_CACHE_NO_INTERNAL_STORE);
|
||||
|
||||
SSL_CTX_sess_set_cache_size(ssl->ctx, 1);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
cache_mode = SSL_SESS_CACHE_SERVER;
|
||||
|
||||
if (shm_zone && builtin_session_cache == NGX_SSL_NO_BUILTIN_SCACHE) {
|
||||
cache_mode |= SSL_SESS_CACHE_NO_INTERNAL;
|
||||
}
|
||||
|
||||
SSL_CTX_set_session_cache_mode(ssl->ctx, cache_mode);
|
||||
|
||||
if (builtin_session_cache != NGX_SSL_NO_BUILTIN_SCACHE) {
|
||||
|
||||
if (builtin_session_cache != NGX_SSL_DFLT_BUILTIN_SCACHE) {
|
||||
SSL_CTX_sess_set_cache_size(ssl->ctx, builtin_session_cache);
|
||||
}
|
||||
}
|
||||
|
||||
if (shm_zone) {
|
||||
SSL_CTX_sess_set_new_cb(ssl->ctx, ngx_ssl_new_session);
|
||||
SSL_CTX_sess_set_get_cb(ssl->ctx, ngx_ssl_get_cached_session);
|
||||
SSL_CTX_sess_set_remove_cb(ssl->ctx, ngx_ssl_remove_session);
|
||||
|
||||
if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_cache_index, shm_zone)
|
||||
== 0)
|
||||
{
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set_ex_data() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_session_id_context(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
|
||||
ngx_array_t *certificates)
|
||||
{
|
||||
int n, i;
|
||||
X509 *cert;
|
||||
X509_NAME *name;
|
||||
ngx_str_t *certs;
|
||||
ngx_uint_t k;
|
||||
EVP_MD_CTX *md;
|
||||
unsigned int len;
|
||||
STACK_OF(X509_NAME) *list;
|
||||
u_char buf[EVP_MAX_MD_SIZE];
|
||||
|
||||
/*
|
||||
* Session ID context is set based on the string provided,
|
||||
* the server certificates, and the client CA list.
|
||||
*/
|
||||
|
||||
md = EVP_MD_CTX_create();
|
||||
if (md == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (EVP_DigestInit_ex(md, EVP_sha1(), NULL) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"EVP_DigestInit_ex() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (EVP_DigestUpdate(md, sess_ctx->data, sess_ctx->len) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"EVP_DigestUpdate() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
|
||||
cert;
|
||||
cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index))
|
||||
{
|
||||
if (X509_digest(cert, EVP_sha1(), buf, &len) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"X509_digest() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (EVP_DigestUpdate(md, buf, len) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"EVP_DigestUpdate() failed");
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
if (SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index) == NULL
|
||||
&& certificates != NULL)
|
||||
{
|
||||
/*
|
||||
* If certificates are loaded dynamically, we use certificate
|
||||
* names as specified in the configuration (with variables).
|
||||
*/
|
||||
|
||||
certs = certificates->elts;
|
||||
for (k = 0; k < certificates->nelts; k++) {
|
||||
|
||||
if (EVP_DigestUpdate(md, certs[k].data, certs[k].len) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"EVP_DigestUpdate() failed");
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list = SSL_CTX_get_client_CA_list(ssl->ctx);
|
||||
|
||||
if (list != NULL) {
|
||||
n = sk_X509_NAME_num(list);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
name = sk_X509_NAME_value(list, i);
|
||||
|
||||
if (X509_NAME_digest(name, EVP_sha1(), buf, &len) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"X509_NAME_digest() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (EVP_DigestUpdate(md, buf, len) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"EVP_DigestUpdate() failed");
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (EVP_DigestFinal_ex(md, buf, &len) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"EVP_DigestFinal_ex() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
EVP_MD_CTX_destroy(md);
|
||||
|
||||
if (SSL_CTX_set_session_id_context(ssl->ctx, buf, len) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set_session_id_context() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
failed:
|
||||
|
||||
EVP_MD_CTX_destroy(md);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data)
|
||||
{
|
||||
size_t len;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_ssl_session_cache_t *cache;
|
||||
|
||||
if (data) {
|
||||
shm_zone->data = data;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
|
||||
if (shm_zone->shm.exists) {
|
||||
shm_zone->data = shpool->data;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
cache = ngx_slab_alloc(shpool, sizeof(ngx_ssl_session_cache_t));
|
||||
if (cache == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
shpool->data = cache;
|
||||
shm_zone->data = cache;
|
||||
|
||||
ngx_rbtree_init(&cache->session_rbtree, &cache->sentinel,
|
||||
ngx_ssl_session_rbtree_insert_value);
|
||||
|
||||
ngx_queue_init(&cache->expire_queue);
|
||||
|
||||
cache->ticket_keys[0].expire = 0;
|
||||
cache->ticket_keys[1].expire = 0;
|
||||
cache->ticket_keys[2].expire = 0;
|
||||
|
||||
cache->fail_time = 0;
|
||||
|
||||
len = sizeof(" in SSL session shared cache \"\"") + shm_zone->shm.name.len;
|
||||
|
||||
shpool->log_ctx = ngx_slab_alloc(shpool, len);
|
||||
if (shpool->log_ctx == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_sprintf(shpool->log_ctx, " in SSL session shared cache \"%V\"%Z",
|
||||
&shm_zone->shm.name);
|
||||
|
||||
shpool->log_nomem = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The length of the session id is 16 bytes for SSLv2 sessions and
|
||||
* between 1 and 32 bytes for SSLv3 and TLS, typically 32 bytes.
|
||||
* Typical length of the external ASN1 representation of a session
|
||||
* is about 150 bytes plus SNI server name.
|
||||
*
|
||||
* On 32-bit platforms we allocate an rbtree node, a session id, and
|
||||
* an ASN1 representation in a single allocation, it typically takes
|
||||
* 256 bytes.
|
||||
*
|
||||
* On 64-bit platforms we allocate separately an rbtree node + session_id,
|
||||
* and an ASN1 representation, they take accordingly 128 and 256 bytes.
|
||||
*
|
||||
* OpenSSL's i2d_SSL_SESSION() and d2i_SSL_SESSION are slow,
|
||||
* so they are outside the code locked by shared pool mutex
|
||||
*/
|
||||
|
||||
static int
|
||||
ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)
|
||||
{
|
||||
int len;
|
||||
u_char *p, *session_id;
|
||||
size_t n;
|
||||
uint32_t hash;
|
||||
SSL_CTX *ssl_ctx;
|
||||
unsigned int session_id_length;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
ngx_connection_t *c;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_ssl_sess_id_t *sess_id;
|
||||
ngx_ssl_session_cache_t *cache;
|
||||
u_char buf[NGX_SSL_MAX_SESSION_SIZE];
|
||||
|
||||
#ifdef TLS1_3_VERSION
|
||||
|
||||
/*
|
||||
* OpenSSL tries to save TLSv1.3 sessions into session cache
|
||||
* even when using tickets for stateless session resumption,
|
||||
* "because some applications just want to know about the creation
|
||||
* of a session"; do not cache such sessions
|
||||
*/
|
||||
|
||||
if (SSL_version(ssl_conn) == TLS1_3_VERSION
|
||||
&& (SSL_get_options(ssl_conn) & SSL_OP_NO_TICKET) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
len = i2d_SSL_SESSION(sess, NULL);
|
||||
|
||||
/* do not cache too big session */
|
||||
|
||||
if (len > NGX_SSL_MAX_SESSION_SIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
p = buf;
|
||||
i2d_SSL_SESSION(sess, &p);
|
||||
|
||||
session_id = (u_char *) SSL_SESSION_get_id(sess, &session_id_length);
|
||||
|
||||
/* do not cache sessions with too long session id */
|
||||
|
||||
if (session_id_length > 32) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
c = ngx_ssl_get_connection(ssl_conn);
|
||||
|
||||
ssl_ctx = c->ssl->session_ctx;
|
||||
shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index);
|
||||
|
||||
cache = shm_zone->data;
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
|
||||
/* drop one or two expired sessions */
|
||||
ngx_ssl_expire_sessions(cache, shpool, 1);
|
||||
|
||||
#if (NGX_PTR_SIZE == 8)
|
||||
n = sizeof(ngx_ssl_sess_id_t);
|
||||
#else
|
||||
n = offsetof(ngx_ssl_sess_id_t, session) + len;
|
||||
#endif
|
||||
|
||||
sess_id = ngx_slab_alloc_locked(shpool, n);
|
||||
|
||||
if (sess_id == NULL) {
|
||||
|
||||
/* drop the oldest non-expired session and try once more */
|
||||
|
||||
ngx_ssl_expire_sessions(cache, shpool, 0);
|
||||
|
||||
sess_id = ngx_slab_alloc_locked(shpool, n);
|
||||
|
||||
if (sess_id == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
#if (NGX_PTR_SIZE == 8)
|
||||
|
||||
sess_id->session = ngx_slab_alloc_locked(shpool, len);
|
||||
|
||||
if (sess_id->session == NULL) {
|
||||
|
||||
/* drop the oldest non-expired session and try once more */
|
||||
|
||||
ngx_ssl_expire_sessions(cache, shpool, 0);
|
||||
|
||||
sess_id->session = ngx_slab_alloc_locked(shpool, len);
|
||||
|
||||
if (sess_id->session == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
ngx_memcpy(sess_id->session, buf, len);
|
||||
ngx_memcpy(sess_id->id, session_id, session_id_length);
|
||||
|
||||
hash = ngx_crc32_short(session_id, session_id_length);
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"ssl new session: %08XD:%ud:%d",
|
||||
hash, session_id_length, len);
|
||||
|
||||
sess_id->node.key = hash;
|
||||
sess_id->node.data = (u_char) session_id_length;
|
||||
sess_id->len = len;
|
||||
|
||||
sess_id->expire = ngx_time() + SSL_CTX_get_timeout(ssl_ctx);
|
||||
|
||||
ngx_queue_insert_head(&cache->expire_queue, &sess_id->queue);
|
||||
|
||||
ngx_rbtree_insert(&cache->session_rbtree, &sess_id->node);
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
|
||||
if (sess_id) {
|
||||
ngx_slab_free_locked(shpool, sess_id);
|
||||
}
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
if (cache->fail_time != ngx_time()) {
|
||||
cache->fail_time = ngx_time();
|
||||
ngx_log_error(NGX_LOG_WARN, c->log, 0,
|
||||
"could not allocate new session%s", shpool->log_ctx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ngx_ssl_session_t *
|
||||
ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn,
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100003L
|
||||
const
|
||||
#endif
|
||||
u_char *id, int len, int *copy)
|
||||
{
|
||||
size_t slen;
|
||||
uint32_t hash;
|
||||
ngx_int_t rc;
|
||||
const u_char *p;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_rbtree_node_t *node, *sentinel;
|
||||
ngx_ssl_session_t *sess;
|
||||
ngx_ssl_sess_id_t *sess_id;
|
||||
ngx_ssl_session_cache_t *cache;
|
||||
u_char buf[NGX_SSL_MAX_SESSION_SIZE];
|
||||
ngx_connection_t *c;
|
||||
|
||||
hash = ngx_crc32_short((u_char *) (uintptr_t) id, (size_t) len);
|
||||
*copy = 0;
|
||||
|
||||
c = ngx_ssl_get_connection(ssl_conn);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"ssl get session: %08XD:%d", hash, len);
|
||||
|
||||
shm_zone = SSL_CTX_get_ex_data(c->ssl->session_ctx,
|
||||
ngx_ssl_session_cache_index);
|
||||
|
||||
cache = shm_zone->data;
|
||||
|
||||
sess = NULL;
|
||||
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
|
||||
node = cache->session_rbtree.root;
|
||||
sentinel = cache->session_rbtree.sentinel;
|
||||
|
||||
while (node != sentinel) {
|
||||
|
||||
if (hash < node->key) {
|
||||
node = node->left;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hash > node->key) {
|
||||
node = node->right;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* hash == node->key */
|
||||
|
||||
sess_id = (ngx_ssl_sess_id_t *) node;
|
||||
|
||||
rc = ngx_memn2cmp((u_char *) (uintptr_t) id, sess_id->id,
|
||||
(size_t) len, (size_t) node->data);
|
||||
|
||||
if (rc == 0) {
|
||||
|
||||
if (sess_id->expire > ngx_time()) {
|
||||
slen = sess_id->len;
|
||||
|
||||
ngx_memcpy(buf, sess_id->session, slen);
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
p = buf;
|
||||
sess = d2i_SSL_SESSION(NULL, &p, slen);
|
||||
|
||||
return sess;
|
||||
}
|
||||
|
||||
ngx_queue_remove(&sess_id->queue);
|
||||
|
||||
ngx_rbtree_delete(&cache->session_rbtree, node);
|
||||
|
||||
ngx_explicit_memzero(sess_id->session, sess_id->len);
|
||||
|
||||
#if (NGX_PTR_SIZE == 8)
|
||||
ngx_slab_free_locked(shpool, sess_id->session);
|
||||
#endif
|
||||
ngx_slab_free_locked(shpool, sess_id);
|
||||
|
||||
sess = NULL;
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
node = (rc < 0) ? node->left : node->right;
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
return sess;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_ssl_remove_cached_session(SSL_CTX *ssl, ngx_ssl_session_t *sess)
|
||||
{
|
||||
SSL_CTX_remove_session(ssl, sess);
|
||||
|
||||
ngx_ssl_remove_session(ssl, sess);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess)
|
||||
{
|
||||
u_char *id;
|
||||
uint32_t hash;
|
||||
ngx_int_t rc;
|
||||
unsigned int len;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_rbtree_node_t *node, *sentinel;
|
||||
ngx_ssl_sess_id_t *sess_id;
|
||||
ngx_ssl_session_cache_t *cache;
|
||||
|
||||
shm_zone = SSL_CTX_get_ex_data(ssl, ngx_ssl_session_cache_index);
|
||||
|
||||
if (shm_zone == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache = shm_zone->data;
|
||||
|
||||
id = (u_char *) SSL_SESSION_get_id(sess, &len);
|
||||
|
||||
hash = ngx_crc32_short(id, len);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
|
||||
"ssl remove session: %08XD:%ud", hash, len);
|
||||
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
|
||||
node = cache->session_rbtree.root;
|
||||
sentinel = cache->session_rbtree.sentinel;
|
||||
|
||||
while (node != sentinel) {
|
||||
|
||||
if (hash < node->key) {
|
||||
node = node->left;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hash > node->key) {
|
||||
node = node->right;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* hash == node->key */
|
||||
|
||||
sess_id = (ngx_ssl_sess_id_t *) node;
|
||||
|
||||
rc = ngx_memn2cmp(id, sess_id->id, len, (size_t) node->data);
|
||||
|
||||
if (rc == 0) {
|
||||
|
||||
ngx_queue_remove(&sess_id->queue);
|
||||
|
||||
ngx_rbtree_delete(&cache->session_rbtree, node);
|
||||
|
||||
ngx_explicit_memzero(sess_id->session, sess_id->len);
|
||||
|
||||
#if (NGX_PTR_SIZE == 8)
|
||||
ngx_slab_free_locked(shpool, sess_id->session);
|
||||
#endif
|
||||
ngx_slab_free_locked(shpool, sess_id);
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
node = (rc < 0) ? node->left : node->right;
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
|
||||
ngx_slab_pool_t *shpool, ngx_uint_t n)
|
||||
{
|
||||
time_t now;
|
||||
ngx_queue_t *q;
|
||||
ngx_ssl_sess_id_t *sess_id;
|
||||
|
||||
now = ngx_time();
|
||||
|
||||
while (n < 3) {
|
||||
|
||||
if (ngx_queue_empty(&cache->expire_queue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
q = ngx_queue_last(&cache->expire_queue);
|
||||
|
||||
sess_id = ngx_queue_data(q, ngx_ssl_sess_id_t, queue);
|
||||
|
||||
if (n++ != 0 && sess_id->expire > now) {
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_queue_remove(q);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
|
||||
"expire session: %08Xi", sess_id->node.key);
|
||||
|
||||
ngx_rbtree_delete(&cache->session_rbtree, &sess_id->node);
|
||||
|
||||
ngx_explicit_memzero(sess_id->session, sess_id->len);
|
||||
|
||||
#if (NGX_PTR_SIZE == 8)
|
||||
ngx_slab_free_locked(shpool, sess_id->session);
|
||||
#endif
|
||||
ngx_slab_free_locked(shpool, sess_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp,
|
||||
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
|
||||
{
|
||||
ngx_rbtree_node_t **p;
|
||||
ngx_ssl_sess_id_t *sess_id, *sess_id_temp;
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
if (node->key < temp->key) {
|
||||
|
||||
p = &temp->left;
|
||||
|
||||
} else if (node->key > temp->key) {
|
||||
|
||||
p = &temp->right;
|
||||
|
||||
} else { /* node->key == temp->key */
|
||||
|
||||
sess_id = (ngx_ssl_sess_id_t *) node;
|
||||
sess_id_temp = (ngx_ssl_sess_id_t *) temp;
|
||||
|
||||
p = (ngx_memn2cmp(sess_id->id, sess_id_temp->id,
|
||||
(size_t) node->data, (size_t) temp->data)
|
||||
< 0) ? &temp->left : &temp->right;
|
||||
}
|
||||
|
||||
if (*p == sentinel) {
|
||||
break;
|
||||
}
|
||||
|
||||
temp = *p;
|
||||
}
|
||||
|
||||
*p = node;
|
||||
node->parent = temp;
|
||||
node->left = sentinel;
|
||||
node->right = sentinel;
|
||||
ngx_rbt_red(node);
|
||||
}
|
||||
|
||||
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths)
|
||||
{
|
||||
u_char buf[80];
|
||||
size_t size;
|
||||
ssize_t n;
|
||||
ngx_str_t *path;
|
||||
ngx_file_t file;
|
||||
ngx_uint_t i;
|
||||
ngx_array_t *keys;
|
||||
ngx_file_info_t fi;
|
||||
ngx_pool_cleanup_t *cln;
|
||||
ngx_ssl_ticket_key_t *key;
|
||||
|
||||
if (paths == NULL
|
||||
&& SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_session_cache_index) == NULL)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
keys = ngx_array_create(cf->pool, paths ? paths->nelts : 3,
|
||||
sizeof(ngx_ssl_ticket_key_t));
|
||||
if (keys == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cln = ngx_pool_cleanup_add(cf->pool, 0);
|
||||
if (cln == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cln->handler = ngx_ssl_ticket_keys_cleanup;
|
||||
cln->data = keys;
|
||||
|
||||
if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_ticket_keys_index, keys) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set_ex_data() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (SSL_CTX_set_tlsext_ticket_key_cb(ssl->ctx, ngx_ssl_ticket_key_callback)
|
||||
== 0)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_WARN, cf->log, 0,
|
||||
"nginx was built with Session Tickets support, however, "
|
||||
"now it is linked dynamically to an OpenSSL library "
|
||||
"which has no tlsext support, therefore Session Tickets "
|
||||
"are not available");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (paths == NULL) {
|
||||
|
||||
/* placeholder for keys in shared memory */
|
||||
|
||||
key = ngx_array_push_n(keys, 3);
|
||||
key[0].shared = 1;
|
||||
key[0].expire = 0;
|
||||
key[1].shared = 1;
|
||||
key[1].expire = 0;
|
||||
key[2].shared = 1;
|
||||
key[2].expire = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
path = paths->elts;
|
||||
for (i = 0; i < paths->nelts; i++) {
|
||||
|
||||
if (ngx_conf_full_name(cf->cycle, &path[i], 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memzero(&file, sizeof(ngx_file_t));
|
||||
file.name = path[i];
|
||||
file.log = cf->log;
|
||||
|
||||
file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
|
||||
NGX_FILE_OPEN, 0);
|
||||
|
||||
if (file.fd == NGX_INVALID_FILE) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
|
||||
ngx_open_file_n " \"%V\" failed", &file.name);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
|
||||
ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
|
||||
ngx_fd_info_n " \"%V\" failed", &file.name);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
size = ngx_file_size(&fi);
|
||||
|
||||
if (size != 48 && size != 80) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"\"%V\" must be 48 or 80 bytes", &file.name);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
n = ngx_read_file(&file, buf, size, 0);
|
||||
|
||||
if (n == NGX_ERROR) {
|
||||
ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
|
||||
ngx_read_file_n " \"%V\" failed", &file.name);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if ((size_t) n != size) {
|
||||
ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
|
||||
ngx_read_file_n " \"%V\" returned only "
|
||||
"%z bytes instead of %uz", &file.name, n, size);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
key = ngx_array_push(keys);
|
||||
if (key == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
key->shared = 0;
|
||||
key->expire = 1;
|
||||
|
||||
if (size == 48) {
|
||||
key->size = 48;
|
||||
ngx_memcpy(key->name, buf, 16);
|
||||
ngx_memcpy(key->aes_key, buf + 16, 16);
|
||||
ngx_memcpy(key->hmac_key, buf + 32, 16);
|
||||
|
||||
} else {
|
||||
key->size = 80;
|
||||
ngx_memcpy(key->name, buf, 16);
|
||||
ngx_memcpy(key->hmac_key, buf + 16, 32);
|
||||
ngx_memcpy(key->aes_key, buf + 48, 32);
|
||||
}
|
||||
|
||||
if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
|
||||
ngx_close_file_n " \"%V\" failed", &file.name);
|
||||
}
|
||||
|
||||
ngx_explicit_memzero(&buf, 80);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
failed:
|
||||
|
||||
if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
|
||||
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
|
||||
ngx_close_file_n " \"%V\" failed", &file.name);
|
||||
}
|
||||
|
||||
ngx_explicit_memzero(&buf, 80);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
ngx_ssl_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
|
||||
unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
|
||||
HMAC_CTX *hctx, int enc)
|
||||
{
|
||||
size_t size;
|
||||
SSL_CTX *ssl_ctx;
|
||||
ngx_uint_t i;
|
||||
ngx_array_t *keys;
|
||||
ngx_connection_t *c;
|
||||
ngx_ssl_ticket_key_t *key;
|
||||
const EVP_MD *digest;
|
||||
const EVP_CIPHER *cipher;
|
||||
|
||||
c = ngx_ssl_get_connection(ssl_conn);
|
||||
ssl_ctx = c->ssl->session_ctx;
|
||||
|
||||
if (ngx_ssl_rotate_ticket_keys(ssl_ctx, c->log) != NGX_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef OPENSSL_NO_SHA256
|
||||
digest = EVP_sha1();
|
||||
#else
|
||||
digest = EVP_sha256();
|
||||
#endif
|
||||
|
||||
keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ticket_keys_index);
|
||||
if (keys == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
key = keys->elts;
|
||||
|
||||
if (enc == 1) {
|
||||
/* encrypt session ticket */
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"ssl ticket encrypt, key: \"%*xs\" (%s session)",
|
||||
(size_t) 16, key[0].name,
|
||||
SSL_session_reused(ssl_conn) ? "reused" : "new");
|
||||
|
||||
if (key[0].size == 48) {
|
||||
cipher = EVP_aes_128_cbc();
|
||||
size = 16;
|
||||
|
||||
} else {
|
||||
cipher = EVP_aes_256_cbc();
|
||||
size = 32;
|
||||
}
|
||||
|
||||
if (RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "RAND_bytes() failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptInit_ex(ectx, cipher, NULL, key[0].aes_key, iv) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0,
|
||||
"EVP_EncryptInit_ex() failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
|
||||
if (HMAC_Init_ex(hctx, key[0].hmac_key, size, digest, NULL) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "HMAC_Init_ex() failed");
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
HMAC_Init_ex(hctx, key[0].hmac_key, size, digest, NULL);
|
||||
#endif
|
||||
|
||||
ngx_memcpy(name, key[0].name, 16);
|
||||
|
||||
return 1;
|
||||
|
||||
} else {
|
||||
/* decrypt session ticket */
|
||||
|
||||
for (i = 0; i < keys->nelts; i++) {
|
||||
if (ngx_memcmp(name, key[i].name, 16) == 0) {
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"ssl ticket decrypt, key: \"%*xs\" not found",
|
||||
(size_t) 16, name);
|
||||
|
||||
return 0;
|
||||
|
||||
found:
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"ssl ticket decrypt, key: \"%*xs\"%s",
|
||||
(size_t) 16, key[i].name, (i == 0) ? " (default)" : "");
|
||||
|
||||
if (key[i].size == 48) {
|
||||
cipher = EVP_aes_128_cbc();
|
||||
size = 16;
|
||||
|
||||
} else {
|
||||
cipher = EVP_aes_256_cbc();
|
||||
size = 32;
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
|
||||
if (HMAC_Init_ex(hctx, key[i].hmac_key, size, digest, NULL) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "HMAC_Init_ex() failed");
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
HMAC_Init_ex(hctx, key[i].hmac_key, size, digest, NULL);
|
||||
#endif
|
||||
|
||||
if (EVP_DecryptInit_ex(ectx, cipher, NULL, key[i].aes_key, iv) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0,
|
||||
"EVP_DecryptInit_ex() failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* renew if TLSv1.3 */
|
||||
|
||||
#ifdef TLS1_3_VERSION
|
||||
if (SSL_version(ssl_conn) == TLS1_3_VERSION) {
|
||||
return 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* renew if non-default key */
|
||||
|
||||
if (i != 0 && key[i].expire) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_rotate_ticket_keys(SSL_CTX *ssl_ctx, ngx_log_t *log)
|
||||
{
|
||||
time_t now, expire;
|
||||
ngx_array_t *keys;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_ssl_ticket_key_t *key;
|
||||
ngx_ssl_session_cache_t *cache;
|
||||
u_char buf[80];
|
||||
|
||||
keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ticket_keys_index);
|
||||
if (keys == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
key = keys->elts;
|
||||
|
||||
if (!key[0].shared) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* if we don't need to update expiration of the current key
|
||||
* and the previous key is still needed, don't sync with shared
|
||||
* memory to save some work; in the worst case other worker process
|
||||
* will switch to the next key, but this process will still be able
|
||||
* to decrypt tickets encrypted with it
|
||||
*/
|
||||
|
||||
now = ngx_time();
|
||||
expire = now + SSL_CTX_get_timeout(ssl_ctx);
|
||||
|
||||
if (key[0].expire >= expire && key[1].expire >= now) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index);
|
||||
|
||||
cache = shm_zone->data;
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
|
||||
key = cache->ticket_keys;
|
||||
|
||||
if (key[0].expire == 0) {
|
||||
|
||||
/* initialize the current key */
|
||||
|
||||
if (RAND_bytes(buf, 80) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed");
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
key[0].shared = 1;
|
||||
key[0].expire = expire;
|
||||
key[0].size = 80;
|
||||
ngx_memcpy(key[0].name, buf, 16);
|
||||
ngx_memcpy(key[0].hmac_key, buf + 16, 32);
|
||||
ngx_memcpy(key[0].aes_key, buf + 48, 32);
|
||||
|
||||
ngx_explicit_memzero(&buf, 80);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
|
||||
"ssl ticket key: \"%*xs\"",
|
||||
(size_t) 16, key[0].name);
|
||||
|
||||
/*
|
||||
* copy the current key to the next key, as initialization of
|
||||
* the previous key will replace the current key with the next
|
||||
* key
|
||||
*/
|
||||
|
||||
key[2] = key[0];
|
||||
}
|
||||
|
||||
if (key[1].expire < now) {
|
||||
|
||||
/*
|
||||
* if the previous key is no longer needed (or not initialized),
|
||||
* replace it with the current key, replace the current key with
|
||||
* the next key, and generate new next key
|
||||
*/
|
||||
|
||||
key[1] = key[0];
|
||||
key[0] = key[2];
|
||||
|
||||
if (RAND_bytes(buf, 80) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed");
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
key[2].shared = 1;
|
||||
key[2].expire = 0;
|
||||
key[2].size = 80;
|
||||
ngx_memcpy(key[2].name, buf, 16);
|
||||
ngx_memcpy(key[2].hmac_key, buf + 16, 32);
|
||||
ngx_memcpy(key[2].aes_key, buf + 48, 32);
|
||||
|
||||
ngx_explicit_memzero(&buf, 80);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
|
||||
"ssl ticket key: \"%*xs\"",
|
||||
(size_t) 16, key[2].name);
|
||||
}
|
||||
|
||||
/*
|
||||
* update expiration of the current key: it is going to be needed
|
||||
* at least till the session being created expires
|
||||
*/
|
||||
|
||||
if (expire > key[0].expire) {
|
||||
key[0].expire = expire;
|
||||
}
|
||||
|
||||
/* sync keys to the worker process memory */
|
||||
|
||||
ngx_memcpy(keys->elts, cache->ticket_keys,
|
||||
2 * sizeof(ngx_ssl_ticket_key_t));
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ticket_keys_cleanup(void *data)
|
||||
{
|
||||
ngx_array_t *keys = data;
|
||||
|
||||
ngx_explicit_memzero(keys->elts,
|
||||
keys->nelts * sizeof(ngx_ssl_ticket_key_t));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths)
|
||||
{
|
||||
if (paths) {
|
||||
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
|
||||
"\"ssl_session_ticket_key\" ignored, not supported");
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
void
|
||||
ngx_ssl_cleanup_ctx(void *data)
|
||||
{
|
||||
ngx_ssl_t *ssl = data;
|
||||
|
||||
X509 *cert, *next;
|
||||
|
||||
cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
|
||||
|
||||
while (cert) {
|
||||
next = X509_get_ex_data(cert, ngx_ssl_next_certificate_index);
|
||||
X509_free(cert);
|
||||
cert = next;
|
||||
}
|
||||
|
||||
SSL_CTX_free(ssl->ctx);
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_check_host(ngx_connection_t *c, ngx_str_t *name)
|
||||
{
|
||||
X509 *cert;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#ifdef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
|
||||
|
||||
/* X509_check_host() is only available in OpenSSL 1.0.2+ */
|
||||
|
||||
if (name->len == 0) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (X509_check_host(cert, (char *) name->data, name->len, 0, NULL) != 1) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"X509_check_host(): no match");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"X509_check_host(): match");
|
||||
|
||||
goto found;
|
||||
|
||||
#else
|
||||
{
|
||||
int n, i;
|
||||
X509_NAME *sname;
|
||||
ASN1_STRING *str;
|
||||
X509_NAME_ENTRY *entry;
|
||||
GENERAL_NAME *altname;
|
||||
STACK_OF(GENERAL_NAME) *altnames;
|
||||
|
||||
/*
|
||||
* As per RFC6125 and RFC2818, we check subjectAltName extension,
|
||||
* and if it's not present - commonName in Subject is checked.
|
||||
*/
|
||||
|
||||
altnames = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
|
||||
|
||||
if (altnames) {
|
||||
n = sk_GENERAL_NAME_num(altnames);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
altname = sk_GENERAL_NAME_value(altnames, i);
|
||||
|
||||
if (altname->type != GEN_DNS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
str = altname->d.dNSName;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL subjectAltName: \"%*s\"",
|
||||
ASN1_STRING_length(str), ASN1_STRING_data(str));
|
||||
|
||||
if (ngx_ssl_check_name(name, str) == NGX_OK) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL subjectAltName: match");
|
||||
GENERAL_NAMES_free(altnames);
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL subjectAltName: no match");
|
||||
|
||||
GENERAL_NAMES_free(altnames);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is no subjectAltName extension, check commonName
|
||||
* in Subject. While RFC2818 requires to only check "most specific"
|
||||
* CN, both Apache and OpenSSL check all CNs, and so do we.
|
||||
*/
|
||||
|
||||
sname = X509_get_subject_name(cert);
|
||||
|
||||
if (sname == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
i = -1;
|
||||
for ( ;; ) {
|
||||
i = X509_NAME_get_index_by_NID(sname, NID_commonName, i);
|
||||
|
||||
if (i < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
entry = X509_NAME_get_entry(sname, i);
|
||||
str = X509_NAME_ENTRY_get_data(entry);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL commonName: \"%*s\"",
|
||||
ASN1_STRING_length(str), ASN1_STRING_data(str));
|
||||
|
||||
if (ngx_ssl_check_name(name, str) == NGX_OK) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL commonName: match");
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL commonName: no match");
|
||||
}
|
||||
#endif
|
||||
|
||||
failed:
|
||||
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
|
||||
found:
|
||||
|
||||
X509_free(cert);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_check_name(ngx_str_t *name, ASN1_STRING *pattern)
|
||||
{
|
||||
u_char *s, *p, *end;
|
||||
size_t slen, plen;
|
||||
|
||||
s = name->data;
|
||||
slen = name->len;
|
||||
|
||||
p = ASN1_STRING_data(pattern);
|
||||
plen = ASN1_STRING_length(pattern);
|
||||
|
||||
if (slen == plen && ngx_strncasecmp(s, p, plen) == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (plen > 2 && p[0] == '*' && p[1] == '.') {
|
||||
plen -= 1;
|
||||
p += 1;
|
||||
|
||||
end = s + slen;
|
||||
s = ngx_strlchr(s, end, '.');
|
||||
|
||||
if (s == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
slen = end - s;
|
||||
|
||||
if (plen == slen && ngx_strncasecmp(s, p, plen) == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_protocol(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
s->data = (u_char *) SSL_get_version(c->ssl->connection);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_cipher_name(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
s->data = (u_char *) SSL_get_cipher_name(c->ssl->connection);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_ciphers(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
#ifdef SSL_CTRL_GET_RAW_CIPHERLIST
|
||||
|
||||
int n, i, bytes;
|
||||
size_t len;
|
||||
u_char *ciphers, *p;
|
||||
const SSL_CIPHER *cipher;
|
||||
|
||||
bytes = SSL_get0_raw_cipherlist(c->ssl->connection, NULL);
|
||||
n = SSL_get0_raw_cipherlist(c->ssl->connection, &ciphers);
|
||||
|
||||
if (n <= 0) {
|
||||
s->len = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
len = 0;
|
||||
n /= bytes;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
cipher = SSL_CIPHER_find(c->ssl->connection, ciphers + i * bytes);
|
||||
|
||||
if (cipher) {
|
||||
len += ngx_strlen(SSL_CIPHER_get_name(cipher));
|
||||
|
||||
} else {
|
||||
len += sizeof("0x") - 1 + bytes * (sizeof("00") - 1);
|
||||
}
|
||||
|
||||
len += sizeof(":") - 1;
|
||||
}
|
||||
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
p = s->data;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
cipher = SSL_CIPHER_find(c->ssl->connection, ciphers + i * bytes);
|
||||
|
||||
if (cipher) {
|
||||
p = ngx_sprintf(p, "%s", SSL_CIPHER_get_name(cipher));
|
||||
|
||||
} else {
|
||||
p = ngx_sprintf(p, "0x");
|
||||
p = ngx_hex_dump(p, ciphers + i * bytes, bytes);
|
||||
}
|
||||
|
||||
*p++ = ':';
|
||||
}
|
||||
|
||||
p--;
|
||||
|
||||
s->len = p - s->data;
|
||||
|
||||
#else
|
||||
|
||||
u_char buf[4096];
|
||||
|
||||
if (SSL_get_shared_ciphers(c->ssl->connection, (char *) buf, 4096)
|
||||
== NULL)
|
||||
{
|
||||
s->len = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
s->len = ngx_strlen(buf);
|
||||
s->data = ngx_pnalloc(pool, s->len);
|
||||
if (s->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(s->data, buf, s->len);
|
||||
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_curve(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
#ifdef SSL_get_negotiated_group
|
||||
|
||||
int nid;
|
||||
|
||||
nid = SSL_get_negotiated_group(c->ssl->connection);
|
||||
|
||||
if (nid != NID_undef) {
|
||||
|
||||
if ((nid & TLSEXT_nid_unknown) == 0) {
|
||||
s->len = ngx_strlen(OBJ_nid2sn(nid));
|
||||
s->data = (u_char *) OBJ_nid2sn(nid);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
s->len = sizeof("0x0000") - 1;
|
||||
|
||||
s->data = ngx_pnalloc(pool, s->len);
|
||||
if (s->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_sprintf(s->data, "0x%04xd", nid & 0xffff);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
s->len = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_curves(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
#ifdef SSL_CTRL_GET_CURVES
|
||||
|
||||
int *curves, n, i, nid;
|
||||
u_char *p;
|
||||
size_t len;
|
||||
|
||||
n = SSL_get1_curves(c->ssl->connection, NULL);
|
||||
|
||||
if (n <= 0) {
|
||||
s->len = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
curves = ngx_palloc(pool, n * sizeof(int));
|
||||
|
||||
n = SSL_get1_curves(c->ssl->connection, curves);
|
||||
len = 0;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
nid = curves[i];
|
||||
|
||||
if (nid & TLSEXT_nid_unknown) {
|
||||
len += sizeof("0x0000") - 1;
|
||||
|
||||
} else {
|
||||
len += ngx_strlen(OBJ_nid2sn(nid));
|
||||
}
|
||||
|
||||
len += sizeof(":") - 1;
|
||||
}
|
||||
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
p = s->data;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
nid = curves[i];
|
||||
|
||||
if (nid & TLSEXT_nid_unknown) {
|
||||
p = ngx_sprintf(p, "0x%04xd", nid & 0xffff);
|
||||
|
||||
} else {
|
||||
p = ngx_sprintf(p, "%s", OBJ_nid2sn(nid));
|
||||
}
|
||||
|
||||
*p++ = ':';
|
||||
}
|
||||
|
||||
p--;
|
||||
|
||||
s->len = p - s->data;
|
||||
|
||||
#else
|
||||
|
||||
s->len = 0;
|
||||
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_session_id(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
u_char *buf;
|
||||
SSL_SESSION *sess;
|
||||
unsigned int len;
|
||||
|
||||
sess = SSL_get0_session(c->ssl->connection);
|
||||
if (sess == NULL) {
|
||||
s->len = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
buf = (u_char *) SSL_SESSION_get_id(sess, &len);
|
||||
|
||||
s->len = 2 * len;
|
||||
s->data = ngx_pnalloc(pool, 2 * len);
|
||||
if (s->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_hex_dump(s->data, buf, len);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_session_reused(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
if (SSL_session_reused(c->ssl->connection)) {
|
||||
ngx_str_set(s, "r");
|
||||
|
||||
} else {
|
||||
ngx_str_set(s, ".");
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_early_data(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
s->len = 0;
|
||||
|
||||
#ifdef SSL_ERROR_EARLY_DATA_REJECTED
|
||||
|
||||
/* BoringSSL */
|
||||
|
||||
if (SSL_in_early_data(c->ssl->connection)) {
|
||||
ngx_str_set(s, "1");
|
||||
}
|
||||
|
||||
#elif defined SSL_READ_EARLY_DATA_SUCCESS
|
||||
|
||||
/* OpenSSL */
|
||||
|
||||
if (!SSL_is_init_finished(c->ssl->connection)) {
|
||||
ngx_str_set(s, "1");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_server_name(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
|
||||
size_t len;
|
||||
const char *name;
|
||||
|
||||
name = SSL_get_servername(c->ssl->connection, TLSEXT_NAMETYPE_host_name);
|
||||
|
||||
if (name) {
|
||||
len = ngx_strlen(name);
|
||||
|
||||
s->len = len;
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(s->data, name, len);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
s->len = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_alpn_protocol(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
|
||||
|
||||
unsigned int len;
|
||||
const unsigned char *data;
|
||||
|
||||
SSL_get0_alpn_selected(c->ssl->connection, &data, &len);
|
||||
|
||||
if (len > 0) {
|
||||
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(s->data, data, len);
|
||||
s->len = len;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
s->len = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
size_t len;
|
||||
BIO *bio;
|
||||
X509 *cert;
|
||||
|
||||
s->len = 0;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if (bio == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (PEM_write_bio_X509(bio, cert) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "PEM_write_bio_X509() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
len = BIO_pending(bio);
|
||||
s->len = len;
|
||||
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
BIO_read(bio, s->data, len);
|
||||
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
failed:
|
||||
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
u_char *p;
|
||||
size_t len;
|
||||
ngx_uint_t i;
|
||||
ngx_str_t cert;
|
||||
|
||||
if (ngx_ssl_get_raw_certificate(c, pool, &cert) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (cert.len == 0) {
|
||||
s->len = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
len = cert.len - 1;
|
||||
|
||||
for (i = 0; i < cert.len - 1; i++) {
|
||||
if (cert.data[i] == LF) {
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
s->len = len;
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
p = s->data;
|
||||
|
||||
for (i = 0; i < cert.len - 1; i++) {
|
||||
*p++ = cert.data[i];
|
||||
if (cert.data[i] == LF) {
|
||||
*p++ = '\t';
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_escaped_certificate(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s)
|
||||
{
|
||||
ngx_str_t cert;
|
||||
uintptr_t n;
|
||||
|
||||
if (ngx_ssl_get_raw_certificate(c, pool, &cert) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (cert.len == 0) {
|
||||
s->len = 0;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
n = ngx_escape_uri(NULL, cert.data, cert.len, NGX_ESCAPE_URI_COMPONENT);
|
||||
|
||||
s->len = cert.len + n * 2;
|
||||
s->data = ngx_pnalloc(pool, s->len);
|
||||
if (s->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_escape_uri(s->data, cert.data, cert.len, NGX_ESCAPE_URI_COMPONENT);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
BIO *bio;
|
||||
X509 *cert;
|
||||
X509_NAME *name;
|
||||
|
||||
s->len = 0;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
name = X509_get_subject_name(cert);
|
||||
if (name == NULL) {
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if (bio == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253) < 0) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "X509_NAME_print_ex() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
s->len = BIO_pending(bio);
|
||||
s->data = ngx_pnalloc(pool, s->len);
|
||||
if (s->data == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
BIO_read(bio, s->data, s->len);
|
||||
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
failed:
|
||||
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_issuer_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
BIO *bio;
|
||||
X509 *cert;
|
||||
X509_NAME *name;
|
||||
|
||||
s->len = 0;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
name = X509_get_issuer_name(cert);
|
||||
if (name == NULL) {
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if (bio == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253) < 0) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "X509_NAME_print_ex() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
s->len = BIO_pending(bio);
|
||||
s->data = ngx_pnalloc(pool, s->len);
|
||||
if (s->data == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
BIO_read(bio, s->data, s->len);
|
||||
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
failed:
|
||||
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_subject_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s)
|
||||
{
|
||||
char *p;
|
||||
size_t len;
|
||||
X509 *cert;
|
||||
X509_NAME *name;
|
||||
|
||||
s->len = 0;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
name = X509_get_subject_name(cert);
|
||||
if (name == NULL) {
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
p = X509_NAME_oneline(name, NULL, 0);
|
||||
if (p == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "X509_NAME_oneline() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
for (len = 0; p[len]; len++) { /* void */ }
|
||||
|
||||
s->len = len;
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
OPENSSL_free(p);
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(s->data, p, len);
|
||||
|
||||
OPENSSL_free(p);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_issuer_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s)
|
||||
{
|
||||
char *p;
|
||||
size_t len;
|
||||
X509 *cert;
|
||||
X509_NAME *name;
|
||||
|
||||
s->len = 0;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
name = X509_get_issuer_name(cert);
|
||||
if (name == NULL) {
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
p = X509_NAME_oneline(name, NULL, 0);
|
||||
if (p == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "X509_NAME_oneline() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
for (len = 0; p[len]; len++) { /* void */ }
|
||||
|
||||
s->len = len;
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
OPENSSL_free(p);
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(s->data, p, len);
|
||||
|
||||
OPENSSL_free(p);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_serial_number(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
size_t len;
|
||||
X509 *cert;
|
||||
BIO *bio;
|
||||
|
||||
s->len = 0;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if (bio == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
i2a_ASN1_INTEGER(bio, X509_get_serialNumber(cert));
|
||||
len = BIO_pending(bio);
|
||||
|
||||
s->len = len;
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
BIO_read(bio, s->data, len);
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_fingerprint(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
X509 *cert;
|
||||
unsigned int len;
|
||||
u_char buf[EVP_MAX_MD_SIZE];
|
||||
|
||||
s->len = 0;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (!X509_digest(cert, EVP_sha1(), buf, &len)) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "X509_digest() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
s->len = 2 * len;
|
||||
s->data = ngx_pnalloc(pool, 2 * len);
|
||||
if (s->data == NULL) {
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_hex_dump(s->data, buf, len);
|
||||
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_client_verify(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
X509 *cert;
|
||||
long rc;
|
||||
const char *str;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
ngx_str_set(s, "NONE");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
X509_free(cert);
|
||||
|
||||
rc = SSL_get_verify_result(c->ssl->connection);
|
||||
|
||||
if (rc == X509_V_OK) {
|
||||
if (ngx_ssl_ocsp_get_status(c, &str) == NGX_OK) {
|
||||
ngx_str_set(s, "SUCCESS");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
} else {
|
||||
str = X509_verify_cert_error_string(rc);
|
||||
}
|
||||
|
||||
s->data = ngx_pnalloc(pool, sizeof("FAILED:") - 1 + ngx_strlen(str));
|
||||
if (s->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
s->len = ngx_sprintf(s->data, "FAILED:%s", str) - s->data;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_client_v_start(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
BIO *bio;
|
||||
X509 *cert;
|
||||
size_t len;
|
||||
|
||||
s->len = 0;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if (bio == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER > 0x10100000L
|
||||
ASN1_TIME_print(bio, X509_get0_notBefore(cert));
|
||||
#else
|
||||
ASN1_TIME_print(bio, X509_get_notBefore(cert));
|
||||
#endif
|
||||
|
||||
len = BIO_pending(bio);
|
||||
|
||||
s->len = len;
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
BIO_read(bio, s->data, len);
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_client_v_end(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
BIO *bio;
|
||||
X509 *cert;
|
||||
size_t len;
|
||||
|
||||
s->len = 0;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if (bio == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER > 0x10100000L
|
||||
ASN1_TIME_print(bio, X509_get0_notAfter(cert));
|
||||
#else
|
||||
ASN1_TIME_print(bio, X509_get_notAfter(cert));
|
||||
#endif
|
||||
|
||||
len = BIO_pending(bio);
|
||||
|
||||
s->len = len;
|
||||
s->data = ngx_pnalloc(pool, len);
|
||||
if (s->data == NULL) {
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
BIO_read(bio, s->data, len);
|
||||
BIO_free(bio);
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
|
||||
{
|
||||
X509 *cert;
|
||||
time_t now, end;
|
||||
|
||||
s->len = 0;
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER > 0x10100000L
|
||||
end = ngx_ssl_parse_time(X509_get0_notAfter(cert), c->log);
|
||||
#else
|
||||
end = ngx_ssl_parse_time(X509_get_notAfter(cert), c->log);
|
||||
#endif
|
||||
|
||||
if (end == (time_t) NGX_ERROR) {
|
||||
X509_free(cert);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
now = ngx_time();
|
||||
|
||||
if (end < now + 86400) {
|
||||
ngx_str_set(s, "0");
|
||||
X509_free(cert);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
s->data = ngx_pnalloc(pool, NGX_TIME_T_LEN);
|
||||
if (s->data == NULL) {
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
s->len = ngx_sprintf(s->data, "%T", (end - now) / 86400) - s->data;
|
||||
|
||||
X509_free(cert);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static time_t
|
||||
ngx_ssl_parse_time(
|
||||
#if OPENSSL_VERSION_NUMBER > 0x10100000L
|
||||
const
|
||||
#endif
|
||||
ASN1_TIME *asn1time, ngx_log_t *log)
|
||||
{
|
||||
BIO *bio;
|
||||
char *value;
|
||||
size_t len;
|
||||
time_t time;
|
||||
|
||||
/*
|
||||
* OpenSSL doesn't provide a way to convert ASN1_TIME
|
||||
* into time_t. To do this, we use ASN1_TIME_print(),
|
||||
* which uses the "MMM DD HH:MM:SS YYYY [GMT]" format (e.g.,
|
||||
* "Feb 3 00:55:52 2015 GMT"), and parse the result.
|
||||
*/
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if (bio == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, log, 0, "BIO_new() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* fake weekday prepended to match C asctime() format */
|
||||
|
||||
BIO_write(bio, "Tue ", sizeof("Tue ") - 1);
|
||||
ASN1_TIME_print(bio, asn1time);
|
||||
len = BIO_get_mem_data(bio, &value);
|
||||
|
||||
time = ngx_parse_http_time((u_char *) value, len);
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_openssl_create_conf(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_openssl_conf_t *oscf;
|
||||
|
||||
oscf = ngx_pcalloc(cycle->pool, sizeof(ngx_openssl_conf_t));
|
||||
if (oscf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* set by ngx_pcalloc():
|
||||
*
|
||||
* oscf->engine = 0;
|
||||
*/
|
||||
|
||||
return oscf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
#ifndef OPENSSL_NO_ENGINE
|
||||
|
||||
ngx_openssl_conf_t *oscf = conf;
|
||||
|
||||
ENGINE *engine;
|
||||
ngx_str_t *value;
|
||||
|
||||
if (oscf->engine) {
|
||||
return "is duplicate";
|
||||
}
|
||||
|
||||
oscf->engine = 1;
|
||||
|
||||
value = cf->args->elts;
|
||||
|
||||
engine = ENGINE_by_id((char *) value[1].data);
|
||||
|
||||
if (engine == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
|
||||
"ENGINE_by_id(\"%V\") failed", &value[1]);
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
if (ENGINE_set_default(engine, ENGINE_METHOD_ALL) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
|
||||
"ENGINE_set_default(\"%V\", ENGINE_METHOD_ALL) failed",
|
||||
&value[1]);
|
||||
|
||||
ENGINE_free(engine);
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ENGINE_free(engine);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
|
||||
#else
|
||||
|
||||
return "is not supported";
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_openssl_exit(ngx_cycle_t *cycle)
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100003L
|
||||
|
||||
EVP_cleanup();
|
||||
#ifndef OPENSSL_NO_ENGINE
|
||||
ENGINE_cleanup();
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_EVENT_OPENSSL_H_INCLUDED_
|
||||
#define _NGX_EVENT_OPENSSL_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
#define OPENSSL_SUPPRESS_DEPRECATED
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/dh.h>
|
||||
#ifndef OPENSSL_NO_ENGINE
|
||||
#include <openssl/engine.h>
|
||||
#endif
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#ifndef OPENSSL_NO_OCSP
|
||||
#include <openssl/ocsp.h>
|
||||
#endif
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#define NGX_SSL_NAME "OpenSSL"
|
||||
|
||||
|
||||
#if (defined LIBRESSL_VERSION_NUMBER && OPENSSL_VERSION_NUMBER == 0x20000000L)
|
||||
#undef OPENSSL_VERSION_NUMBER
|
||||
#if (LIBRESSL_VERSION_NUMBER >= 0x2080000fL)
|
||||
#define OPENSSL_VERSION_NUMBER 0x1010000fL
|
||||
#else
|
||||
#define OPENSSL_VERSION_NUMBER 0x1000107fL
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100001L)
|
||||
|
||||
#define ngx_ssl_version() OpenSSL_version(OPENSSL_VERSION)
|
||||
|
||||
#else
|
||||
|
||||
#define ngx_ssl_version() SSLeay_version(SSLEAY_VERSION)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#define ngx_ssl_session_t SSL_SESSION
|
||||
#define ngx_ssl_conn_t SSL
|
||||
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
|
||||
#define SSL_is_server(s) (s)->server
|
||||
#endif
|
||||
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined SSL_get_peer_certificate)
|
||||
#define SSL_get_peer_certificate(s) SSL_get1_peer_certificate(s)
|
||||
#endif
|
||||
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x30000000L && !defined ERR_peek_error_data)
|
||||
#define ERR_peek_error_data(d, f) ERR_peek_error_line_data(NULL, NULL, d, f)
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t;
|
||||
|
||||
|
||||
struct ngx_ssl_s {
|
||||
SSL_CTX *ctx;
|
||||
ngx_log_t *log;
|
||||
size_t buffer_size;
|
||||
};
|
||||
|
||||
|
||||
struct ngx_ssl_connection_s {
|
||||
ngx_ssl_conn_t *connection;
|
||||
SSL_CTX *session_ctx;
|
||||
|
||||
ngx_int_t last;
|
||||
ngx_buf_t *buf;
|
||||
size_t buffer_size;
|
||||
|
||||
ngx_connection_handler_pt handler;
|
||||
|
||||
ngx_ssl_session_t *session;
|
||||
ngx_connection_handler_pt save_session;
|
||||
|
||||
ngx_event_handler_pt saved_read_handler;
|
||||
ngx_event_handler_pt saved_write_handler;
|
||||
|
||||
ngx_ssl_ocsp_t *ocsp;
|
||||
|
||||
u_char early_buf;
|
||||
|
||||
unsigned handshaked:1;
|
||||
unsigned handshake_rejected:1;
|
||||
unsigned renegotiation:1;
|
||||
unsigned buffer:1;
|
||||
unsigned sendfile:1;
|
||||
unsigned no_wait_shutdown:1;
|
||||
unsigned no_send_shutdown:1;
|
||||
unsigned shutdown_without_free:1;
|
||||
unsigned handshake_buffer_set:1;
|
||||
unsigned session_timeout_set:1;
|
||||
unsigned try_early_data:1;
|
||||
unsigned in_early:1;
|
||||
unsigned in_ocsp:1;
|
||||
unsigned early_preread:1;
|
||||
unsigned write_blocked:1;
|
||||
};
|
||||
|
||||
|
||||
#define NGX_SSL_NO_SCACHE -2
|
||||
#define NGX_SSL_NONE_SCACHE -3
|
||||
#define NGX_SSL_NO_BUILTIN_SCACHE -4
|
||||
#define NGX_SSL_DFLT_BUILTIN_SCACHE -5
|
||||
|
||||
|
||||
#define NGX_SSL_MAX_SESSION_SIZE 4096
|
||||
|
||||
typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t;
|
||||
|
||||
struct ngx_ssl_sess_id_s {
|
||||
ngx_rbtree_node_t node;
|
||||
size_t len;
|
||||
ngx_queue_t queue;
|
||||
time_t expire;
|
||||
u_char id[32];
|
||||
#if (NGX_PTR_SIZE == 8)
|
||||
u_char *session;
|
||||
#else
|
||||
u_char session[1];
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
u_char name[16];
|
||||
u_char hmac_key[32];
|
||||
u_char aes_key[32];
|
||||
time_t expire;
|
||||
unsigned size:8;
|
||||
unsigned shared:1;
|
||||
} ngx_ssl_ticket_key_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_rbtree_t session_rbtree;
|
||||
ngx_rbtree_node_t sentinel;
|
||||
ngx_queue_t expire_queue;
|
||||
ngx_ssl_ticket_key_t ticket_keys[3];
|
||||
time_t fail_time;
|
||||
} ngx_ssl_session_cache_t;
|
||||
|
||||
|
||||
#define NGX_SSL_SSLv2 0x0002
|
||||
#define NGX_SSL_SSLv3 0x0004
|
||||
#define NGX_SSL_TLSv1 0x0008
|
||||
#define NGX_SSL_TLSv1_1 0x0010
|
||||
#define NGX_SSL_TLSv1_2 0x0020
|
||||
#define NGX_SSL_TLSv1_3 0x0040
|
||||
|
||||
|
||||
#define NGX_SSL_BUFFER 1
|
||||
#define NGX_SSL_CLIENT 2
|
||||
|
||||
#define NGX_SSL_BUFSIZE 16384
|
||||
|
||||
|
||||
ngx_int_t ngx_ssl_init(ngx_log_t *log);
|
||||
ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data);
|
||||
|
||||
ngx_int_t ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_array_t *certs, ngx_array_t *keys, ngx_array_t *passwords);
|
||||
ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords);
|
||||
ngx_int_t ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords);
|
||||
|
||||
ngx_int_t ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers,
|
||||
ngx_uint_t prefer_server_ciphers);
|
||||
ngx_int_t ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_str_t *cert, ngx_int_t depth);
|
||||
ngx_int_t ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_str_t *cert, ngx_int_t depth);
|
||||
ngx_int_t ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl);
|
||||
ngx_int_t ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify);
|
||||
ngx_int_t ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_resolver_t *resolver, ngx_msec_t resolver_timeout);
|
||||
ngx_int_t ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,
|
||||
ngx_uint_t depth, ngx_shm_zone_t *shm_zone);
|
||||
ngx_int_t ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_resolver_t *resolver, ngx_msec_t resolver_timeout);
|
||||
|
||||
ngx_int_t ngx_ssl_ocsp_validate(ngx_connection_t *c);
|
||||
ngx_int_t ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s);
|
||||
void ngx_ssl_ocsp_cleanup(ngx_connection_t *c);
|
||||
ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data);
|
||||
|
||||
ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file);
|
||||
ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf,
|
||||
ngx_array_t *passwords);
|
||||
ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file);
|
||||
ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name);
|
||||
ngx_int_t ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_uint_t enable);
|
||||
ngx_int_t ngx_ssl_conf_commands(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_array_t *commands);
|
||||
|
||||
ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_uint_t enable);
|
||||
ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
|
||||
ngx_array_t *certificates, ssize_t builtin_session_cache,
|
||||
ngx_shm_zone_t *shm_zone, time_t timeout);
|
||||
ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_array_t *paths);
|
||||
ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data);
|
||||
|
||||
ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c,
|
||||
ngx_uint_t flags);
|
||||
|
||||
void ngx_ssl_remove_cached_session(SSL_CTX *ssl, ngx_ssl_session_t *sess);
|
||||
ngx_int_t ngx_ssl_set_session(ngx_connection_t *c, ngx_ssl_session_t *session);
|
||||
ngx_ssl_session_t *ngx_ssl_get_session(ngx_connection_t *c);
|
||||
ngx_ssl_session_t *ngx_ssl_get0_session(ngx_connection_t *c);
|
||||
#define ngx_ssl_free_session SSL_SESSION_free
|
||||
#define ngx_ssl_get_connection(ssl_conn) \
|
||||
SSL_get_ex_data(ssl_conn, ngx_ssl_connection_index)
|
||||
#define ngx_ssl_get_server_conf(ssl_ctx) \
|
||||
SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_server_conf_index)
|
||||
|
||||
#define ngx_ssl_verify_error_optional(n) \
|
||||
(n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT \
|
||||
|| n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN \
|
||||
|| n == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY \
|
||||
|| n == X509_V_ERR_CERT_UNTRUSTED \
|
||||
|| n == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE)
|
||||
|
||||
ngx_int_t ngx_ssl_check_host(ngx_connection_t *c, ngx_str_t *name);
|
||||
|
||||
|
||||
ngx_int_t ngx_ssl_get_protocol(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_cipher_name(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_ciphers(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_curve(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_curves(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_session_id(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_session_reused(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_early_data(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_server_name(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_alpn_protocol(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_certificate(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_escaped_certificate(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_issuer_dn(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_subject_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_issuer_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_serial_number(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_fingerprint(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_client_verify(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_client_v_start(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_client_v_end(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
ngx_int_t ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool,
|
||||
ngx_str_t *s);
|
||||
|
||||
|
||||
ngx_int_t ngx_ssl_handshake(ngx_connection_t *c);
|
||||
ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size);
|
||||
ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size);
|
||||
ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit);
|
||||
ngx_chain_t *ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in,
|
||||
off_t limit);
|
||||
void ngx_ssl_free_buffer(ngx_connection_t *c);
|
||||
ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c);
|
||||
void ngx_cdecl ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
|
||||
char *fmt, ...);
|
||||
void ngx_ssl_cleanup_ctx(void *data);
|
||||
|
||||
|
||||
extern int ngx_ssl_connection_index;
|
||||
extern int ngx_ssl_server_conf_index;
|
||||
extern int ngx_ssl_session_cache_index;
|
||||
extern int ngx_ssl_ticket_keys_index;
|
||||
extern int ngx_ssl_ocsp_index;
|
||||
extern int ngx_ssl_certificate_index;
|
||||
extern int ngx_ssl_next_certificate_index;
|
||||
extern int ngx_ssl_certificate_name_index;
|
||||
extern int ngx_ssl_stapling_index;
|
||||
|
||||
|
||||
#endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
|
||||
@@ -0,0 +1,2778 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Maxim Dounin
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
#include <ngx_event_connect.h>
|
||||
|
||||
|
||||
#if (!defined OPENSSL_NO_OCSP && defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB)
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t staple;
|
||||
ngx_msec_t timeout;
|
||||
|
||||
ngx_resolver_t *resolver;
|
||||
ngx_msec_t resolver_timeout;
|
||||
|
||||
ngx_addr_t *addrs;
|
||||
ngx_uint_t naddrs;
|
||||
ngx_str_t host;
|
||||
ngx_str_t uri;
|
||||
in_port_t port;
|
||||
|
||||
SSL_CTX *ssl_ctx;
|
||||
|
||||
X509 *cert;
|
||||
X509 *issuer;
|
||||
STACK_OF(X509) *chain;
|
||||
|
||||
u_char *name;
|
||||
|
||||
time_t valid;
|
||||
time_t refresh;
|
||||
|
||||
unsigned verify:1;
|
||||
unsigned loading:1;
|
||||
} ngx_ssl_stapling_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_addr_t *addrs;
|
||||
ngx_uint_t naddrs;
|
||||
|
||||
ngx_str_t host;
|
||||
ngx_str_t uri;
|
||||
in_port_t port;
|
||||
ngx_uint_t depth;
|
||||
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
|
||||
ngx_resolver_t *resolver;
|
||||
ngx_msec_t resolver_timeout;
|
||||
} ngx_ssl_ocsp_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_rbtree_t rbtree;
|
||||
ngx_rbtree_node_t sentinel;
|
||||
ngx_queue_t expire_queue;
|
||||
} ngx_ssl_ocsp_cache_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_node_t node;
|
||||
ngx_queue_t queue;
|
||||
int status;
|
||||
time_t valid;
|
||||
} ngx_ssl_ocsp_cache_node_t;
|
||||
|
||||
|
||||
typedef struct ngx_ssl_ocsp_ctx_s ngx_ssl_ocsp_ctx_t;
|
||||
|
||||
|
||||
struct ngx_ssl_ocsp_s {
|
||||
STACK_OF(X509) *certs;
|
||||
ngx_uint_t ncert;
|
||||
|
||||
int cert_status;
|
||||
ngx_int_t status;
|
||||
|
||||
ngx_ssl_ocsp_conf_t *conf;
|
||||
ngx_ssl_ocsp_ctx_t *ctx;
|
||||
};
|
||||
|
||||
|
||||
struct ngx_ssl_ocsp_ctx_s {
|
||||
SSL_CTX *ssl_ctx;
|
||||
|
||||
X509 *cert;
|
||||
X509 *issuer;
|
||||
STACK_OF(X509) *chain;
|
||||
|
||||
int status;
|
||||
time_t valid;
|
||||
|
||||
u_char *name;
|
||||
|
||||
ngx_uint_t naddrs;
|
||||
ngx_uint_t naddr;
|
||||
|
||||
ngx_addr_t *addrs;
|
||||
ngx_str_t host;
|
||||
ngx_str_t uri;
|
||||
in_port_t port;
|
||||
|
||||
ngx_resolver_t *resolver;
|
||||
ngx_msec_t resolver_timeout;
|
||||
|
||||
ngx_msec_t timeout;
|
||||
|
||||
void (*handler)(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
void *data;
|
||||
|
||||
ngx_str_t key;
|
||||
ngx_buf_t *request;
|
||||
ngx_buf_t *response;
|
||||
ngx_peer_connection_t peer;
|
||||
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
|
||||
ngx_int_t (*process)(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
|
||||
ngx_uint_t state;
|
||||
|
||||
ngx_uint_t code;
|
||||
ngx_uint_t count;
|
||||
ngx_uint_t flags;
|
||||
ngx_uint_t done;
|
||||
|
||||
u_char *header_name_start;
|
||||
u_char *header_name_end;
|
||||
u_char *header_start;
|
||||
u_char *header_end;
|
||||
|
||||
ngx_pool_t *pool;
|
||||
ngx_log_t *log;
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t ngx_ssl_stapling_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
X509 *cert, ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify);
|
||||
static ngx_int_t ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_ssl_stapling_t *staple, ngx_str_t *file);
|
||||
static ngx_int_t ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_ssl_stapling_t *staple);
|
||||
static ngx_int_t ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_ssl_stapling_t *staple, ngx_str_t *responder);
|
||||
|
||||
static int ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn,
|
||||
void *data);
|
||||
static void ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple);
|
||||
static void ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
|
||||
static time_t ngx_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time);
|
||||
|
||||
static void ngx_ssl_stapling_cleanup(void *data);
|
||||
|
||||
static void ngx_ssl_ocsp_validate_next(ngx_connection_t *c);
|
||||
static void ngx_ssl_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static ngx_int_t ngx_ssl_ocsp_responder(ngx_connection_t *c,
|
||||
ngx_ssl_ocsp_ctx_t *ctx);
|
||||
|
||||
static ngx_ssl_ocsp_ctx_t *ngx_ssl_ocsp_start(ngx_log_t *log);
|
||||
static void ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static void ngx_ssl_ocsp_next(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static void ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static void ngx_ssl_ocsp_resolve_handler(ngx_resolver_ctx_t *resolve);
|
||||
static void ngx_ssl_ocsp_connect(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static void ngx_ssl_ocsp_write_handler(ngx_event_t *wev);
|
||||
static void ngx_ssl_ocsp_read_handler(ngx_event_t *rev);
|
||||
static void ngx_ssl_ocsp_dummy_handler(ngx_event_t *ev);
|
||||
|
||||
static ngx_int_t ngx_ssl_ocsp_create_request(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static ngx_int_t ngx_ssl_ocsp_process_status_line(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static ngx_int_t ngx_ssl_ocsp_parse_status_line(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static ngx_int_t ngx_ssl_ocsp_process_headers(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static ngx_int_t ngx_ssl_ocsp_parse_header_line(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static ngx_int_t ngx_ssl_ocsp_process_body(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static ngx_int_t ngx_ssl_ocsp_verify(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
|
||||
static ngx_int_t ngx_ssl_ocsp_cache_lookup(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static ngx_int_t ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
static ngx_int_t ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx);
|
||||
|
||||
static u_char *ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len);
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file,
|
||||
ngx_str_t *responder, ngx_uint_t verify)
|
||||
{
|
||||
X509 *cert;
|
||||
|
||||
for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
|
||||
cert;
|
||||
cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index))
|
||||
{
|
||||
if (ngx_ssl_stapling_certificate(cf, ssl, cert, file, responder, verify)
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
SSL_CTX_set_tlsext_status_cb(ssl->ctx, ngx_ssl_certificate_status_callback);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_stapling_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, X509 *cert,
|
||||
ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_pool_cleanup_t *cln;
|
||||
ngx_ssl_stapling_t *staple;
|
||||
|
||||
staple = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_stapling_t));
|
||||
if (staple == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cln = ngx_pool_cleanup_add(cf->pool, 0);
|
||||
if (cln == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cln->handler = ngx_ssl_stapling_cleanup;
|
||||
cln->data = staple;
|
||||
|
||||
if (X509_set_ex_data(cert, ngx_ssl_stapling_index, staple) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#ifdef SSL_CTRL_SELECT_CURRENT_CERT
|
||||
/* OpenSSL 1.0.2+ */
|
||||
SSL_CTX_select_current_cert(ssl->ctx, cert);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_CTRL_GET_EXTRA_CHAIN_CERTS
|
||||
/* OpenSSL 1.0.1+ */
|
||||
SSL_CTX_get_extra_chain_certs(ssl->ctx, &staple->chain);
|
||||
#else
|
||||
staple->chain = ssl->ctx->extra_certs;
|
||||
#endif
|
||||
|
||||
staple->ssl_ctx = ssl->ctx;
|
||||
staple->timeout = 60000;
|
||||
staple->verify = verify;
|
||||
staple->cert = cert;
|
||||
staple->name = X509_get_ex_data(staple->cert,
|
||||
ngx_ssl_certificate_name_index);
|
||||
|
||||
if (file->len) {
|
||||
/* use OCSP response from the file */
|
||||
|
||||
if (ngx_ssl_stapling_file(cf, ssl, staple, file) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
rc = ngx_ssl_stapling_issuer(cf, ssl, staple);
|
||||
|
||||
if (rc == NGX_DECLINED) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
rc = ngx_ssl_stapling_responder(cf, ssl, staple, responder);
|
||||
|
||||
if (rc == NGX_DECLINED) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_ssl_stapling_t *staple, ngx_str_t *file)
|
||||
{
|
||||
BIO *bio;
|
||||
int len;
|
||||
u_char *p, *buf;
|
||||
OCSP_RESPONSE *response;
|
||||
|
||||
if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
bio = BIO_new_file((char *) file->data, "rb");
|
||||
if (bio == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"BIO_new_file(\"%s\") failed", file->data);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
response = d2i_OCSP_RESPONSE_bio(bio, NULL);
|
||||
if (response == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"d2i_OCSP_RESPONSE_bio(\"%s\") failed", file->data);
|
||||
BIO_free(bio);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
len = i2d_OCSP_RESPONSE(response, NULL);
|
||||
if (len <= 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"i2d_OCSP_RESPONSE(\"%s\") failed", file->data);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
buf = ngx_alloc(len, ssl->log);
|
||||
if (buf == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
p = buf;
|
||||
len = i2d_OCSP_RESPONSE(response, &p);
|
||||
if (len <= 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"i2d_OCSP_RESPONSE(\"%s\") failed", file->data);
|
||||
ngx_free(buf);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
OCSP_RESPONSE_free(response);
|
||||
BIO_free(bio);
|
||||
|
||||
staple->staple.data = buf;
|
||||
staple->staple.len = len;
|
||||
staple->valid = NGX_MAX_TIME_T_VALUE;
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
failed:
|
||||
|
||||
OCSP_RESPONSE_free(response);
|
||||
BIO_free(bio);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_ssl_stapling_t *staple)
|
||||
{
|
||||
int i, n, rc;
|
||||
X509 *cert, *issuer;
|
||||
X509_STORE *store;
|
||||
X509_STORE_CTX *store_ctx;
|
||||
|
||||
cert = staple->cert;
|
||||
|
||||
n = sk_X509_num(staple->chain);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0,
|
||||
"SSL get issuer: %d extra certs", n);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
issuer = sk_X509_value(staple->chain, i);
|
||||
if (X509_check_issued(issuer, cert) == X509_V_OK) {
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100001L
|
||||
X509_up_ref(issuer);
|
||||
#else
|
||||
CRYPTO_add(&issuer->references, 1, CRYPTO_LOCK_X509);
|
||||
#endif
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0,
|
||||
"SSL get issuer: found %p in extra certs", issuer);
|
||||
|
||||
staple->issuer = issuer;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
store = SSL_CTX_get_cert_store(ssl->ctx);
|
||||
if (store == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_get_cert_store() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
store_ctx = X509_STORE_CTX_new();
|
||||
if (store_ctx == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"X509_STORE_CTX_new() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (X509_STORE_CTX_init(store_ctx, store, NULL, NULL) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"X509_STORE_CTX_init() failed");
|
||||
X509_STORE_CTX_free(store_ctx);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
rc = X509_STORE_CTX_get1_issuer(&issuer, store_ctx, cert);
|
||||
|
||||
if (rc == -1) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"X509_STORE_CTX_get1_issuer() failed");
|
||||
X509_STORE_CTX_free(store_ctx);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (rc == 0) {
|
||||
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
|
||||
"\"ssl_stapling\" ignored, "
|
||||
"issuer certificate not found for certificate \"%s\"",
|
||||
staple->name);
|
||||
X509_STORE_CTX_free(store_ctx);
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
X509_STORE_CTX_free(store_ctx);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0,
|
||||
"SSL get issuer: found %p in cert store", issuer);
|
||||
|
||||
staple->issuer = issuer;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_ssl_stapling_t *staple, ngx_str_t *responder)
|
||||
{
|
||||
char *s;
|
||||
ngx_str_t rsp;
|
||||
ngx_url_t u;
|
||||
STACK_OF(OPENSSL_STRING) *aia;
|
||||
|
||||
if (responder->len == 0) {
|
||||
|
||||
/* extract OCSP responder URL from certificate */
|
||||
|
||||
aia = X509_get1_ocsp(staple->cert);
|
||||
if (aia == NULL) {
|
||||
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
|
||||
"\"ssl_stapling\" ignored, "
|
||||
"no OCSP responder URL in the certificate \"%s\"",
|
||||
staple->name);
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
|
||||
s = sk_OPENSSL_STRING_value(aia, 0);
|
||||
#else
|
||||
s = sk_value(aia, 0);
|
||||
#endif
|
||||
if (s == NULL) {
|
||||
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
|
||||
"\"ssl_stapling\" ignored, "
|
||||
"no OCSP responder URL in the certificate \"%s\"",
|
||||
staple->name);
|
||||
X509_email_free(aia);
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
responder = &rsp;
|
||||
|
||||
responder->len = ngx_strlen(s);
|
||||
responder->data = ngx_palloc(cf->pool, responder->len);
|
||||
if (responder->data == NULL) {
|
||||
X509_email_free(aia);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(responder->data, s, responder->len);
|
||||
X509_email_free(aia);
|
||||
}
|
||||
|
||||
ngx_memzero(&u, sizeof(ngx_url_t));
|
||||
|
||||
u.url = *responder;
|
||||
u.default_port = 80;
|
||||
u.uri_part = 1;
|
||||
|
||||
if (u.url.len > 7
|
||||
&& ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0)
|
||||
{
|
||||
u.url.len -= 7;
|
||||
u.url.data += 7;
|
||||
|
||||
} else {
|
||||
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
|
||||
"\"ssl_stapling\" ignored, "
|
||||
"invalid URL prefix in OCSP responder \"%V\" "
|
||||
"in the certificate \"%s\"",
|
||||
&u.url, staple->name);
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
|
||||
if (u.err) {
|
||||
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
|
||||
"\"ssl_stapling\" ignored, "
|
||||
"%s in OCSP responder \"%V\" "
|
||||
"in the certificate \"%s\"",
|
||||
u.err, &u.url, staple->name);
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
staple->addrs = u.addrs;
|
||||
staple->naddrs = u.naddrs;
|
||||
staple->host = u.host;
|
||||
staple->uri = u.uri;
|
||||
staple->port = u.port;
|
||||
|
||||
if (staple->uri.len == 0) {
|
||||
ngx_str_set(&staple->uri, "/");
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
|
||||
{
|
||||
X509 *cert;
|
||||
ngx_ssl_stapling_t *staple;
|
||||
|
||||
for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
|
||||
cert;
|
||||
cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index))
|
||||
{
|
||||
staple = X509_get_ex_data(cert, ngx_ssl_stapling_index);
|
||||
staple->resolver = resolver;
|
||||
staple->resolver_timeout = resolver_timeout;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn, void *data)
|
||||
{
|
||||
int rc;
|
||||
X509 *cert;
|
||||
u_char *p;
|
||||
ngx_connection_t *c;
|
||||
ngx_ssl_stapling_t *staple;
|
||||
|
||||
c = ngx_ssl_get_connection(ssl_conn);
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"SSL certificate status callback");
|
||||
|
||||
rc = SSL_TLSEXT_ERR_NOACK;
|
||||
|
||||
cert = SSL_get_certificate(ssl_conn);
|
||||
|
||||
if (cert == NULL) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
staple = X509_get_ex_data(cert, ngx_ssl_stapling_index);
|
||||
|
||||
if (staple == NULL) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (staple->staple.len
|
||||
&& staple->valid >= ngx_time())
|
||||
{
|
||||
/* we have to copy ocsp response as OpenSSL will free it by itself */
|
||||
|
||||
p = OPENSSL_malloc(staple->staple.len);
|
||||
if (p == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "OPENSSL_malloc() failed");
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
}
|
||||
|
||||
ngx_memcpy(p, staple->staple.data, staple->staple.len);
|
||||
|
||||
SSL_set_tlsext_status_ocsp_resp(ssl_conn, p, staple->staple.len);
|
||||
|
||||
rc = SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
|
||||
ngx_ssl_stapling_update(staple);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple)
|
||||
{
|
||||
ngx_ssl_ocsp_ctx_t *ctx;
|
||||
|
||||
if (staple->host.len == 0
|
||||
|| staple->loading || staple->refresh >= ngx_time())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
staple->loading = 1;
|
||||
|
||||
ctx = ngx_ssl_ocsp_start(ngx_cycle->log);
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->ssl_ctx = staple->ssl_ctx;
|
||||
ctx->cert = staple->cert;
|
||||
ctx->issuer = staple->issuer;
|
||||
ctx->chain = staple->chain;
|
||||
ctx->name = staple->name;
|
||||
ctx->flags = (staple->verify ? OCSP_TRUSTOTHER : OCSP_NOVERIFY);
|
||||
|
||||
ctx->addrs = staple->addrs;
|
||||
ctx->naddrs = staple->naddrs;
|
||||
ctx->host = staple->host;
|
||||
ctx->uri = staple->uri;
|
||||
ctx->port = staple->port;
|
||||
ctx->timeout = staple->timeout;
|
||||
|
||||
ctx->resolver = staple->resolver;
|
||||
ctx->resolver_timeout = staple->resolver_timeout;
|
||||
|
||||
ctx->handler = ngx_ssl_stapling_ocsp_handler;
|
||||
ctx->data = staple;
|
||||
|
||||
ngx_ssl_ocsp_request(ctx);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
time_t now;
|
||||
ngx_str_t response;
|
||||
ngx_ssl_stapling_t *staple;
|
||||
|
||||
staple = ctx->data;
|
||||
now = ngx_time();
|
||||
|
||||
if (ngx_ssl_ocsp_verify(ctx) != NGX_OK) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ctx->status != V_OCSP_CERTSTATUS_GOOD) {
|
||||
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"certificate status \"%s\" in the OCSP response",
|
||||
OCSP_cert_status_str(ctx->status));
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* copy the response to memory not in ctx->pool */
|
||||
|
||||
response.len = ctx->response->last - ctx->response->pos;
|
||||
response.data = ngx_alloc(response.len, ctx->log);
|
||||
|
||||
if (response.data == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
ngx_memcpy(response.data, ctx->response->pos, response.len);
|
||||
|
||||
if (staple->staple.data) {
|
||||
ngx_free(staple->staple.data);
|
||||
}
|
||||
|
||||
staple->staple = response;
|
||||
staple->valid = ctx->valid;
|
||||
|
||||
/*
|
||||
* refresh before the response expires,
|
||||
* but not earlier than in 5 minutes, and at least in an hour
|
||||
*/
|
||||
|
||||
staple->loading = 0;
|
||||
staple->refresh = ngx_max(ngx_min(ctx->valid - 300, now + 3600), now + 300);
|
||||
|
||||
ngx_ssl_ocsp_done(ctx);
|
||||
return;
|
||||
|
||||
error:
|
||||
|
||||
staple->loading = 0;
|
||||
staple->refresh = now + 300;
|
||||
|
||||
ngx_ssl_ocsp_done(ctx);
|
||||
}
|
||||
|
||||
|
||||
static time_t
|
||||
ngx_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time)
|
||||
{
|
||||
BIO *bio;
|
||||
char *value;
|
||||
size_t len;
|
||||
time_t time;
|
||||
|
||||
/*
|
||||
* OpenSSL doesn't provide a way to convert ASN1_GENERALIZEDTIME
|
||||
* into time_t. To do this, we use ASN1_GENERALIZEDTIME_print(),
|
||||
* which uses the "MMM DD HH:MM:SS YYYY [GMT]" format (e.g.,
|
||||
* "Feb 3 00:55:52 2015 GMT"), and parse the result.
|
||||
*/
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if (bio == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* fake weekday prepended to match C asctime() format */
|
||||
|
||||
BIO_write(bio, "Tue ", sizeof("Tue ") - 1);
|
||||
ASN1_GENERALIZEDTIME_print(bio, asn1time);
|
||||
len = BIO_get_mem_data(bio, &value);
|
||||
|
||||
time = ngx_parse_http_time((u_char *) value, len);
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_stapling_cleanup(void *data)
|
||||
{
|
||||
ngx_ssl_stapling_t *staple = data;
|
||||
|
||||
if (staple->issuer) {
|
||||
X509_free(staple->issuer);
|
||||
}
|
||||
|
||||
if (staple->staple.data) {
|
||||
ngx_free(staple->staple.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,
|
||||
ngx_uint_t depth, ngx_shm_zone_t *shm_zone)
|
||||
{
|
||||
ngx_url_t u;
|
||||
ngx_ssl_ocsp_conf_t *ocf;
|
||||
|
||||
ocf = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_ocsp_conf_t));
|
||||
if (ocf == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ocf->depth = depth;
|
||||
ocf->shm_zone = shm_zone;
|
||||
|
||||
if (responder->len) {
|
||||
ngx_memzero(&u, sizeof(ngx_url_t));
|
||||
|
||||
u.url = *responder;
|
||||
u.default_port = 80;
|
||||
u.uri_part = 1;
|
||||
|
||||
if (u.url.len > 7
|
||||
&& ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0)
|
||||
{
|
||||
u.url.len -= 7;
|
||||
u.url.data += 7;
|
||||
|
||||
} else {
|
||||
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
|
||||
"invalid URL prefix in OCSP responder \"%V\" "
|
||||
"in \"ssl_ocsp_responder\"", &u.url);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
|
||||
if (u.err) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
|
||||
"%s in OCSP responder \"%V\" "
|
||||
"in \"ssl_ocsp_responder\"", u.err, &u.url);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ocf->addrs = u.addrs;
|
||||
ocf->naddrs = u.naddrs;
|
||||
ocf->host = u.host;
|
||||
ocf->uri = u.uri;
|
||||
ocf->port = u.port;
|
||||
}
|
||||
|
||||
if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_ocsp_index, ocf) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"SSL_CTX_set_ex_data() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
|
||||
{
|
||||
ngx_ssl_ocsp_conf_t *ocf;
|
||||
|
||||
ocf = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_ocsp_index);
|
||||
ocf->resolver = resolver;
|
||||
ocf->resolver_timeout = resolver_timeout;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ocsp_validate(ngx_connection_t *c)
|
||||
{
|
||||
X509 *cert;
|
||||
SSL_CTX *ssl_ctx;
|
||||
ngx_int_t rc;
|
||||
X509_STORE *store;
|
||||
X509_STORE_CTX *store_ctx;
|
||||
STACK_OF(X509) *chain;
|
||||
ngx_ssl_ocsp_t *ocsp;
|
||||
ngx_ssl_ocsp_conf_t *ocf;
|
||||
|
||||
if (c->ssl->in_ocsp) {
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
ssl_ctx = SSL_get_SSL_CTX(c->ssl->connection);
|
||||
|
||||
ocf = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ocsp_index);
|
||||
if (ocf == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (SSL_get_verify_result(c->ssl->connection) != X509_V_OK) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
cert = SSL_get_peer_certificate(c->ssl->connection);
|
||||
if (cert == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ocsp = ngx_pcalloc(c->pool, sizeof(ngx_ssl_ocsp_t));
|
||||
if (ocsp == NULL) {
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->ssl->ocsp = ocsp;
|
||||
|
||||
ocsp->status = NGX_AGAIN;
|
||||
ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD;
|
||||
ocsp->conf = ocf;
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER)
|
||||
|
||||
ocsp->certs = SSL_get0_verified_chain(c->ssl->connection);
|
||||
|
||||
if (ocsp->certs) {
|
||||
ocsp->certs = X509_chain_up_ref(ocsp->certs);
|
||||
if (ocsp->certs == NULL) {
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (ocsp->certs == NULL) {
|
||||
store = SSL_CTX_get_cert_store(ssl_ctx);
|
||||
if (store == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
||||
"SSL_CTX_get_cert_store() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
store_ctx = X509_STORE_CTX_new();
|
||||
if (store_ctx == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
||||
"X509_STORE_CTX_new() failed");
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
chain = SSL_get_peer_cert_chain(c->ssl->connection);
|
||||
|
||||
if (X509_STORE_CTX_init(store_ctx, store, cert, chain) == 0) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
||||
"X509_STORE_CTX_init() failed");
|
||||
X509_STORE_CTX_free(store_ctx);
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
rc = X509_verify_cert(store_ctx);
|
||||
if (rc <= 0) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "X509_verify_cert() failed");
|
||||
X509_STORE_CTX_free(store_ctx);
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ocsp->certs = X509_STORE_CTX_get1_chain(store_ctx);
|
||||
if (ocsp->certs == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
||||
"X509_STORE_CTX_get1_chain() failed");
|
||||
X509_STORE_CTX_free(store_ctx);
|
||||
X509_free(cert);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
X509_STORE_CTX_free(store_ctx);
|
||||
}
|
||||
|
||||
X509_free(cert);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"ssl ocsp validate, certs:%d", sk_X509_num(ocsp->certs));
|
||||
|
||||
ngx_ssl_ocsp_validate_next(c);
|
||||
|
||||
if (ocsp->status == NGX_AGAIN) {
|
||||
c->ssl->in_ocsp = 1;
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_validate_next(ngx_connection_t *c)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_uint_t n;
|
||||
ngx_ssl_ocsp_t *ocsp;
|
||||
ngx_ssl_ocsp_ctx_t *ctx;
|
||||
ngx_ssl_ocsp_conf_t *ocf;
|
||||
|
||||
ocsp = c->ssl->ocsp;
|
||||
ocf = ocsp->conf;
|
||||
|
||||
n = sk_X509_num(ocsp->certs);
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
if (ocsp->ncert == n - 1 || (ocf->depth == 2 && ocsp->ncert == 1)) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"ssl ocsp validated, certs:%ui", ocsp->ncert);
|
||||
rc = NGX_OK;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"ssl ocsp validate cert:%ui", ocsp->ncert);
|
||||
|
||||
ctx = ngx_ssl_ocsp_start(c->log);
|
||||
if (ctx == NULL) {
|
||||
rc = NGX_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ocsp->ctx = ctx;
|
||||
|
||||
ctx->ssl_ctx = SSL_get_SSL_CTX(c->ssl->connection);
|
||||
ctx->cert = sk_X509_value(ocsp->certs, ocsp->ncert);
|
||||
ctx->issuer = sk_X509_value(ocsp->certs, ocsp->ncert + 1);
|
||||
ctx->chain = ocsp->certs;
|
||||
|
||||
ctx->resolver = ocf->resolver;
|
||||
ctx->resolver_timeout = ocf->resolver_timeout;
|
||||
|
||||
ctx->handler = ngx_ssl_ocsp_handler;
|
||||
ctx->data = c;
|
||||
|
||||
ctx->shm_zone = ocf->shm_zone;
|
||||
|
||||
ctx->addrs = ocf->addrs;
|
||||
ctx->naddrs = ocf->naddrs;
|
||||
ctx->host = ocf->host;
|
||||
ctx->uri = ocf->uri;
|
||||
ctx->port = ocf->port;
|
||||
|
||||
rc = ngx_ssl_ocsp_responder(c, ctx);
|
||||
if (rc != NGX_OK) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ctx->uri.len == 0) {
|
||||
ngx_str_set(&ctx->uri, "/");
|
||||
}
|
||||
|
||||
ocsp->ncert++;
|
||||
|
||||
rc = ngx_ssl_ocsp_cache_lookup(ctx);
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (rc == NGX_DECLINED) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* rc == NGX_OK */
|
||||
|
||||
if (ctx->status != V_OCSP_CERTSTATUS_GOOD) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp cached status \"%s\"",
|
||||
OCSP_cert_status_str(ctx->status));
|
||||
ocsp->cert_status = ctx->status;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ocsp->ctx = NULL;
|
||||
ngx_ssl_ocsp_done(ctx);
|
||||
}
|
||||
|
||||
ngx_ssl_ocsp_request(ctx);
|
||||
return;
|
||||
|
||||
done:
|
||||
|
||||
ocsp->status = rc;
|
||||
|
||||
if (c->ssl->in_ocsp) {
|
||||
c->ssl->handshaked = 1;
|
||||
c->ssl->handler(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_ssl_ocsp_t *ocsp;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = ctx->data;
|
||||
ocsp = c->ssl->ocsp;
|
||||
ocsp->ctx = NULL;
|
||||
|
||||
rc = ngx_ssl_ocsp_verify(ctx);
|
||||
if (rc != NGX_OK) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
rc = ngx_ssl_ocsp_cache_store(ctx);
|
||||
if (rc != NGX_OK) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ctx->status != V_OCSP_CERTSTATUS_GOOD) {
|
||||
ocsp->cert_status = ctx->status;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ngx_ssl_ocsp_done(ctx);
|
||||
|
||||
ngx_ssl_ocsp_validate_next(c);
|
||||
|
||||
return;
|
||||
|
||||
done:
|
||||
|
||||
ocsp->status = rc;
|
||||
ngx_ssl_ocsp_done(ctx);
|
||||
|
||||
if (c->ssl->in_ocsp) {
|
||||
c->ssl->handshaked = 1;
|
||||
c->ssl->handler(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_responder(ngx_connection_t *c, ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
char *s;
|
||||
ngx_str_t responder;
|
||||
ngx_url_t u;
|
||||
STACK_OF(OPENSSL_STRING) *aia;
|
||||
|
||||
if (ctx->host.len) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* extract OCSP responder URL from certificate */
|
||||
|
||||
aia = X509_get1_ocsp(ctx->cert);
|
||||
if (aia == NULL) {
|
||||
ngx_log_error(NGX_LOG_ERR, c->log, 0,
|
||||
"no OCSP responder URL in certificate");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
|
||||
s = sk_OPENSSL_STRING_value(aia, 0);
|
||||
#else
|
||||
s = sk_value(aia, 0);
|
||||
#endif
|
||||
if (s == NULL) {
|
||||
ngx_log_error(NGX_LOG_ERR, c->log, 0,
|
||||
"no OCSP responder URL in certificate");
|
||||
X509_email_free(aia);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
responder.len = ngx_strlen(s);
|
||||
responder.data = ngx_palloc(ctx->pool, responder.len);
|
||||
if (responder.data == NULL) {
|
||||
X509_email_free(aia);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(responder.data, s, responder.len);
|
||||
X509_email_free(aia);
|
||||
|
||||
ngx_memzero(&u, sizeof(ngx_url_t));
|
||||
|
||||
u.url = responder;
|
||||
u.default_port = 80;
|
||||
u.uri_part = 1;
|
||||
u.no_resolve = 1;
|
||||
|
||||
if (u.url.len > 7
|
||||
&& ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0)
|
||||
{
|
||||
u.url.len -= 7;
|
||||
u.url.data += 7;
|
||||
|
||||
} else {
|
||||
ngx_log_error(NGX_LOG_ERR, c->log, 0,
|
||||
"invalid URL prefix in OCSP responder \"%V\" "
|
||||
"in certificate", &u.url);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_parse_url(ctx->pool, &u) != NGX_OK) {
|
||||
if (u.err) {
|
||||
ngx_log_error(NGX_LOG_ERR, c->log, 0,
|
||||
"%s in OCSP responder \"%V\" in certificate",
|
||||
u.err, &u.url);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (u.host.len == 0) {
|
||||
ngx_log_error(NGX_LOG_ERR, c->log, 0,
|
||||
"empty host in OCSP responder in certificate");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ctx->addrs = u.addrs;
|
||||
ctx->naddrs = u.naddrs;
|
||||
ctx->host = u.host;
|
||||
ctx->uri = u.uri;
|
||||
ctx->port = u.port;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s)
|
||||
{
|
||||
ngx_ssl_ocsp_t *ocsp;
|
||||
|
||||
ocsp = c->ssl->ocsp;
|
||||
if (ocsp == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ocsp->status == NGX_ERROR) {
|
||||
*s = "certificate status request failed";
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
switch (ocsp->cert_status) {
|
||||
|
||||
case V_OCSP_CERTSTATUS_GOOD:
|
||||
return NGX_OK;
|
||||
|
||||
case V_OCSP_CERTSTATUS_REVOKED:
|
||||
*s = "certificate revoked";
|
||||
break;
|
||||
|
||||
default: /* V_OCSP_CERTSTATUS_UNKNOWN */
|
||||
*s = "certificate status unknown";
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_ssl_ocsp_cleanup(ngx_connection_t *c)
|
||||
{
|
||||
ngx_ssl_ocsp_t *ocsp;
|
||||
|
||||
ocsp = c->ssl->ocsp;
|
||||
if (ocsp == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ocsp->ctx) {
|
||||
ngx_ssl_ocsp_done(ocsp->ctx);
|
||||
ocsp->ctx = NULL;
|
||||
}
|
||||
|
||||
if (ocsp->certs) {
|
||||
sk_X509_pop_free(ocsp->certs, X509_free);
|
||||
ocsp->certs = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_ssl_ocsp_ctx_t *
|
||||
ngx_ssl_ocsp_start(ngx_log_t *log)
|
||||
{
|
||||
ngx_pool_t *pool;
|
||||
ngx_ssl_ocsp_ctx_t *ctx;
|
||||
|
||||
pool = ngx_create_pool(2048, log);
|
||||
if (pool == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx = ngx_pcalloc(pool, sizeof(ngx_ssl_ocsp_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
ngx_destroy_pool(pool);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
log = ngx_palloc(pool, sizeof(ngx_log_t));
|
||||
if (log == NULL) {
|
||||
ngx_destroy_pool(pool);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->pool = pool;
|
||||
|
||||
*log = *ctx->pool->log;
|
||||
|
||||
ctx->pool->log = log;
|
||||
ctx->log = log;
|
||||
|
||||
log->handler = ngx_ssl_ocsp_log_error;
|
||||
log->data = ctx;
|
||||
log->action = "requesting certificate status";
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp done");
|
||||
|
||||
if (ctx->peer.connection) {
|
||||
ngx_close_connection(ctx->peer.connection);
|
||||
}
|
||||
|
||||
ngx_destroy_pool(ctx->pool);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_error(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp error");
|
||||
|
||||
ctx->code = 0;
|
||||
ctx->handler(ctx);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_next(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp next");
|
||||
|
||||
if (++ctx->naddr >= ctx->naddrs) {
|
||||
ngx_ssl_ocsp_error(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->request->pos = ctx->request->start;
|
||||
|
||||
if (ctx->response) {
|
||||
ctx->response->last = ctx->response->pos;
|
||||
}
|
||||
|
||||
if (ctx->peer.connection) {
|
||||
ngx_close_connection(ctx->peer.connection);
|
||||
ctx->peer.connection = NULL;
|
||||
}
|
||||
|
||||
ctx->state = 0;
|
||||
ctx->count = 0;
|
||||
ctx->done = 0;
|
||||
|
||||
ngx_ssl_ocsp_connect(ctx);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
ngx_resolver_ctx_t *resolve, temp;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp request");
|
||||
|
||||
if (ngx_ssl_ocsp_create_request(ctx) != NGX_OK) {
|
||||
ngx_ssl_ocsp_error(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->resolver) {
|
||||
/* resolve OCSP responder hostname */
|
||||
|
||||
temp.name = ctx->host;
|
||||
|
||||
resolve = ngx_resolve_start(ctx->resolver, &temp);
|
||||
if (resolve == NULL) {
|
||||
ngx_ssl_ocsp_error(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resolve == NGX_NO_RESOLVER) {
|
||||
if (ctx->naddrs == 0) {
|
||||
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"no resolver defined to resolve %V", &ctx->host);
|
||||
|
||||
ngx_ssl_ocsp_error(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_WARN, ctx->log, 0,
|
||||
"no resolver defined to resolve %V", &ctx->host);
|
||||
goto connect;
|
||||
}
|
||||
|
||||
resolve->name = ctx->host;
|
||||
resolve->handler = ngx_ssl_ocsp_resolve_handler;
|
||||
resolve->data = ctx;
|
||||
resolve->timeout = ctx->resolver_timeout;
|
||||
|
||||
if (ngx_resolve_name(resolve) != NGX_OK) {
|
||||
ngx_ssl_ocsp_error(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
connect:
|
||||
|
||||
ngx_ssl_ocsp_connect(ctx);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_resolve_handler(ngx_resolver_ctx_t *resolve)
|
||||
{
|
||||
ngx_ssl_ocsp_ctx_t *ctx = resolve->data;
|
||||
|
||||
u_char *p;
|
||||
size_t len;
|
||||
socklen_t socklen;
|
||||
ngx_uint_t i;
|
||||
struct sockaddr *sockaddr;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp resolve handler");
|
||||
|
||||
if (resolve->state) {
|
||||
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"%V could not be resolved (%i: %s)",
|
||||
&resolve->name, resolve->state,
|
||||
ngx_resolver_strerror(resolve->state));
|
||||
goto failed;
|
||||
}
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
{
|
||||
u_char text[NGX_SOCKADDR_STRLEN];
|
||||
ngx_str_t addr;
|
||||
|
||||
addr.data = text;
|
||||
|
||||
for (i = 0; i < resolve->naddrs; i++) {
|
||||
addr.len = ngx_sock_ntop(resolve->addrs[i].sockaddr,
|
||||
resolve->addrs[i].socklen,
|
||||
text, NGX_SOCKADDR_STRLEN, 0);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"name was resolved to %V", &addr);
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ctx->naddrs = resolve->naddrs;
|
||||
ctx->addrs = ngx_pcalloc(ctx->pool, ctx->naddrs * sizeof(ngx_addr_t));
|
||||
|
||||
if (ctx->addrs == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (i = 0; i < resolve->naddrs; i++) {
|
||||
|
||||
socklen = resolve->addrs[i].socklen;
|
||||
|
||||
sockaddr = ngx_palloc(ctx->pool, socklen);
|
||||
if (sockaddr == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
ngx_memcpy(sockaddr, resolve->addrs[i].sockaddr, socklen);
|
||||
ngx_inet_set_port(sockaddr, ctx->port);
|
||||
|
||||
ctx->addrs[i].sockaddr = sockaddr;
|
||||
ctx->addrs[i].socklen = socklen;
|
||||
|
||||
p = ngx_pnalloc(ctx->pool, NGX_SOCKADDR_STRLEN);
|
||||
if (p == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1);
|
||||
|
||||
ctx->addrs[i].name.len = len;
|
||||
ctx->addrs[i].name.data = p;
|
||||
}
|
||||
|
||||
ngx_resolve_name_done(resolve);
|
||||
|
||||
ngx_ssl_ocsp_connect(ctx);
|
||||
return;
|
||||
|
||||
failed:
|
||||
|
||||
ngx_resolve_name_done(resolve);
|
||||
ngx_ssl_ocsp_error(ctx);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_connect(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_addr_t *addr;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp connect %ui/%ui", ctx->naddr, ctx->naddrs);
|
||||
|
||||
addr = &ctx->addrs[ctx->naddr];
|
||||
|
||||
ctx->peer.sockaddr = addr->sockaddr;
|
||||
ctx->peer.socklen = addr->socklen;
|
||||
ctx->peer.name = &addr->name;
|
||||
ctx->peer.get = ngx_event_get_peer;
|
||||
ctx->peer.log = ctx->log;
|
||||
ctx->peer.log_error = NGX_ERROR_ERR;
|
||||
|
||||
rc = ngx_event_connect_peer(&ctx->peer);
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp connect peer done");
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
ngx_ssl_ocsp_error(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rc == NGX_BUSY || rc == NGX_DECLINED) {
|
||||
ngx_ssl_ocsp_next(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->peer.connection->data = ctx;
|
||||
ctx->peer.connection->pool = ctx->pool;
|
||||
|
||||
ctx->peer.connection->read->handler = ngx_ssl_ocsp_read_handler;
|
||||
ctx->peer.connection->write->handler = ngx_ssl_ocsp_write_handler;
|
||||
|
||||
ctx->process = ngx_ssl_ocsp_process_status_line;
|
||||
|
||||
if (ctx->timeout) {
|
||||
ngx_add_timer(ctx->peer.connection->read, ctx->timeout);
|
||||
ngx_add_timer(ctx->peer.connection->write, ctx->timeout);
|
||||
}
|
||||
|
||||
if (rc == NGX_OK) {
|
||||
ngx_ssl_ocsp_write_handler(ctx->peer.connection->write);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_write_handler(ngx_event_t *wev)
|
||||
{
|
||||
ssize_t n, size;
|
||||
ngx_connection_t *c;
|
||||
ngx_ssl_ocsp_ctx_t *ctx;
|
||||
|
||||
c = wev->data;
|
||||
ctx = c->data;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0,
|
||||
"ssl ocsp write handler");
|
||||
|
||||
if (wev->timedout) {
|
||||
ngx_log_error(NGX_LOG_ERR, wev->log, NGX_ETIMEDOUT,
|
||||
"OCSP responder timed out");
|
||||
ngx_ssl_ocsp_next(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
size = ctx->request->last - ctx->request->pos;
|
||||
|
||||
n = ngx_send(c, ctx->request->pos, size);
|
||||
|
||||
if (n == NGX_ERROR) {
|
||||
ngx_ssl_ocsp_next(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
ctx->request->pos += n;
|
||||
|
||||
if (n == size) {
|
||||
wev->handler = ngx_ssl_ocsp_dummy_handler;
|
||||
|
||||
if (wev->timer_set) {
|
||||
ngx_del_timer(wev);
|
||||
}
|
||||
|
||||
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
|
||||
ngx_ssl_ocsp_error(ctx);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wev->timer_set && ctx->timeout) {
|
||||
ngx_add_timer(wev, ctx->timeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_read_handler(ngx_event_t *rev)
|
||||
{
|
||||
ssize_t n, size;
|
||||
ngx_int_t rc;
|
||||
ngx_connection_t *c;
|
||||
ngx_ssl_ocsp_ctx_t *ctx;
|
||||
|
||||
c = rev->data;
|
||||
ctx = c->data;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0,
|
||||
"ssl ocsp read handler");
|
||||
|
||||
if (rev->timedout) {
|
||||
ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT,
|
||||
"OCSP responder timed out");
|
||||
ngx_ssl_ocsp_next(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->response == NULL) {
|
||||
ctx->response = ngx_create_temp_buf(ctx->pool, 16384);
|
||||
if (ctx->response == NULL) {
|
||||
ngx_ssl_ocsp_error(ctx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
size = ctx->response->end - ctx->response->last;
|
||||
|
||||
n = ngx_recv(c, ctx->response->last, size);
|
||||
|
||||
if (n > 0) {
|
||||
ctx->response->last += n;
|
||||
|
||||
rc = ctx->process(ctx);
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
ngx_ssl_ocsp_next(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (n == NGX_AGAIN) {
|
||||
|
||||
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
|
||||
ngx_ssl_ocsp_error(ctx);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ctx->done = 1;
|
||||
|
||||
rc = ctx->process(ctx);
|
||||
|
||||
if (rc == NGX_DONE) {
|
||||
/* ctx->handler() was called */
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"OCSP responder prematurely closed connection");
|
||||
|
||||
ngx_ssl_ocsp_next(ctx);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_ssl_ocsp_dummy_handler(ngx_event_t *ev)
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"ssl ocsp dummy handler");
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_create_request(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
int len;
|
||||
u_char *p;
|
||||
uintptr_t escape;
|
||||
ngx_str_t binary, base64;
|
||||
ngx_buf_t *b;
|
||||
OCSP_CERTID *id;
|
||||
OCSP_REQUEST *ocsp;
|
||||
|
||||
ocsp = OCSP_REQUEST_new();
|
||||
if (ocsp == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
|
||||
"OCSP_REQUEST_new() failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
id = OCSP_cert_to_id(NULL, ctx->cert, ctx->issuer);
|
||||
if (id == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
|
||||
"OCSP_cert_to_id() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (OCSP_request_add0_id(ocsp, id) == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
|
||||
"OCSP_request_add0_id() failed");
|
||||
OCSP_CERTID_free(id);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
len = i2d_OCSP_REQUEST(ocsp, NULL);
|
||||
if (len <= 0) {
|
||||
ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
|
||||
"i2d_OCSP_REQUEST() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
binary.len = len;
|
||||
binary.data = ngx_palloc(ctx->pool, len);
|
||||
if (binary.data == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
p = binary.data;
|
||||
len = i2d_OCSP_REQUEST(ocsp, &p);
|
||||
if (len <= 0) {
|
||||
ngx_ssl_error(NGX_LOG_EMERG, ctx->log, 0,
|
||||
"i2d_OCSP_REQUEST() failed");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
base64.len = ngx_base64_encoded_length(binary.len);
|
||||
base64.data = ngx_palloc(ctx->pool, base64.len);
|
||||
if (base64.data == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
ngx_encode_base64(&base64, &binary);
|
||||
|
||||
escape = ngx_escape_uri(NULL, base64.data, base64.len,
|
||||
NGX_ESCAPE_URI_COMPONENT);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp request length %z, escape %d",
|
||||
base64.len, (int) escape);
|
||||
|
||||
len = sizeof("GET ") - 1 + ctx->uri.len + sizeof("/") - 1
|
||||
+ base64.len + 2 * escape + sizeof(" HTTP/1.0" CRLF) - 1
|
||||
+ sizeof("Host: ") - 1 + ctx->host.len + sizeof(CRLF) - 1
|
||||
+ sizeof(CRLF) - 1;
|
||||
|
||||
b = ngx_create_temp_buf(ctx->pool, len);
|
||||
if (b == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
p = b->last;
|
||||
|
||||
p = ngx_cpymem(p, "GET ", sizeof("GET ") - 1);
|
||||
p = ngx_cpymem(p, ctx->uri.data, ctx->uri.len);
|
||||
|
||||
if (ctx->uri.data[ctx->uri.len - 1] != '/') {
|
||||
*p++ = '/';
|
||||
}
|
||||
|
||||
if (escape == 0) {
|
||||
p = ngx_cpymem(p, base64.data, base64.len);
|
||||
|
||||
} else {
|
||||
p = (u_char *) ngx_escape_uri(p, base64.data, base64.len,
|
||||
NGX_ESCAPE_URI_COMPONENT);
|
||||
}
|
||||
|
||||
p = ngx_cpymem(p, " HTTP/1.0" CRLF, sizeof(" HTTP/1.0" CRLF) - 1);
|
||||
p = ngx_cpymem(p, "Host: ", sizeof("Host: ") - 1);
|
||||
p = ngx_cpymem(p, ctx->host.data, ctx->host.len);
|
||||
*p++ = CR; *p++ = LF;
|
||||
|
||||
/* add "\r\n" at the header end */
|
||||
*p++ = CR; *p++ = LF;
|
||||
|
||||
b->last = p;
|
||||
ctx->request = b;
|
||||
|
||||
OCSP_REQUEST_free(ocsp);
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
failed:
|
||||
|
||||
OCSP_REQUEST_free(ocsp);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_process_status_line(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
|
||||
rc = ngx_ssl_ocsp_parse_status_line(ctx);
|
||||
|
||||
if (rc == NGX_OK) {
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp status %ui \"%*s\"",
|
||||
ctx->code,
|
||||
ctx->header_end - ctx->header_start,
|
||||
ctx->header_start);
|
||||
|
||||
ctx->process = ngx_ssl_ocsp_process_headers;
|
||||
return ctx->process(ctx);
|
||||
}
|
||||
|
||||
if (rc == NGX_AGAIN) {
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
/* rc == NGX_ERROR */
|
||||
|
||||
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"OCSP responder sent invalid response");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_parse_status_line(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
u_char ch;
|
||||
u_char *p;
|
||||
ngx_buf_t *b;
|
||||
enum {
|
||||
sw_start = 0,
|
||||
sw_H,
|
||||
sw_HT,
|
||||
sw_HTT,
|
||||
sw_HTTP,
|
||||
sw_first_major_digit,
|
||||
sw_major_digit,
|
||||
sw_first_minor_digit,
|
||||
sw_minor_digit,
|
||||
sw_status,
|
||||
sw_space_after_status,
|
||||
sw_status_text,
|
||||
sw_almost_done
|
||||
} state;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp process status line");
|
||||
|
||||
state = ctx->state;
|
||||
b = ctx->response;
|
||||
|
||||
for (p = b->pos; p < b->last; p++) {
|
||||
ch = *p;
|
||||
|
||||
switch (state) {
|
||||
|
||||
/* "HTTP/" */
|
||||
case sw_start:
|
||||
switch (ch) {
|
||||
case 'H':
|
||||
state = sw_H;
|
||||
break;
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case sw_H:
|
||||
switch (ch) {
|
||||
case 'T':
|
||||
state = sw_HT;
|
||||
break;
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case sw_HT:
|
||||
switch (ch) {
|
||||
case 'T':
|
||||
state = sw_HTT;
|
||||
break;
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case sw_HTT:
|
||||
switch (ch) {
|
||||
case 'P':
|
||||
state = sw_HTTP;
|
||||
break;
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case sw_HTTP:
|
||||
switch (ch) {
|
||||
case '/':
|
||||
state = sw_first_major_digit;
|
||||
break;
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
/* the first digit of major HTTP version */
|
||||
case sw_first_major_digit:
|
||||
if (ch < '1' || ch > '9') {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
state = sw_major_digit;
|
||||
break;
|
||||
|
||||
/* the major HTTP version or dot */
|
||||
case sw_major_digit:
|
||||
if (ch == '.') {
|
||||
state = sw_first_minor_digit;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch < '0' || ch > '9') {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
/* the first digit of minor HTTP version */
|
||||
case sw_first_minor_digit:
|
||||
if (ch < '0' || ch > '9') {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
state = sw_minor_digit;
|
||||
break;
|
||||
|
||||
/* the minor HTTP version or the end of the request line */
|
||||
case sw_minor_digit:
|
||||
if (ch == ' ') {
|
||||
state = sw_status;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch < '0' || ch > '9') {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
/* HTTP status code */
|
||||
case sw_status:
|
||||
if (ch == ' ') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch < '0' || ch > '9') {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ctx->code = ctx->code * 10 + (ch - '0');
|
||||
|
||||
if (++ctx->count == 3) {
|
||||
state = sw_space_after_status;
|
||||
ctx->header_start = p - 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
/* space or end of line */
|
||||
case sw_space_after_status:
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
state = sw_status_text;
|
||||
break;
|
||||
case '.': /* IIS may send 403.1, 403.2, etc */
|
||||
state = sw_status_text;
|
||||
break;
|
||||
case CR:
|
||||
state = sw_almost_done;
|
||||
break;
|
||||
case LF:
|
||||
ctx->header_end = p;
|
||||
goto done;
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
/* any text until end of line */
|
||||
case sw_status_text:
|
||||
switch (ch) {
|
||||
case CR:
|
||||
state = sw_almost_done;
|
||||
break;
|
||||
case LF:
|
||||
ctx->header_end = p;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
/* end of status line */
|
||||
case sw_almost_done:
|
||||
switch (ch) {
|
||||
case LF:
|
||||
ctx->header_end = p - 1;
|
||||
goto done;
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b->pos = p;
|
||||
ctx->state = state;
|
||||
|
||||
return NGX_AGAIN;
|
||||
|
||||
done:
|
||||
|
||||
b->pos = p + 1;
|
||||
ctx->state = sw_start;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_process_headers(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
size_t len;
|
||||
ngx_int_t rc;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp process headers");
|
||||
|
||||
for ( ;; ) {
|
||||
rc = ngx_ssl_ocsp_parse_header_line(ctx);
|
||||
|
||||
if (rc == NGX_OK) {
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp header \"%*s: %*s\"",
|
||||
ctx->header_name_end - ctx->header_name_start,
|
||||
ctx->header_name_start,
|
||||
ctx->header_end - ctx->header_start,
|
||||
ctx->header_start);
|
||||
|
||||
len = ctx->header_name_end - ctx->header_name_start;
|
||||
|
||||
if (len == sizeof("Content-Type") - 1
|
||||
&& ngx_strncasecmp(ctx->header_name_start,
|
||||
(u_char *) "Content-Type",
|
||||
sizeof("Content-Type") - 1)
|
||||
== 0)
|
||||
{
|
||||
len = ctx->header_end - ctx->header_start;
|
||||
|
||||
if (len != sizeof("application/ocsp-response") - 1
|
||||
|| ngx_strncasecmp(ctx->header_start,
|
||||
(u_char *) "application/ocsp-response",
|
||||
sizeof("application/ocsp-response") - 1)
|
||||
!= 0)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"OCSP responder sent invalid "
|
||||
"\"Content-Type\" header: \"%*s\"",
|
||||
ctx->header_end - ctx->header_start,
|
||||
ctx->header_start);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* TODO: honor Content-Length */
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rc == NGX_DONE) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc == NGX_AGAIN) {
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
/* rc == NGX_ERROR */
|
||||
|
||||
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"OCSP responder sent invalid response");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ctx->process = ngx_ssl_ocsp_process_body;
|
||||
return ctx->process(ctx);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_parse_header_line(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
u_char c, ch, *p;
|
||||
enum {
|
||||
sw_start = 0,
|
||||
sw_name,
|
||||
sw_space_before_value,
|
||||
sw_value,
|
||||
sw_space_after_value,
|
||||
sw_almost_done,
|
||||
sw_header_almost_done
|
||||
} state;
|
||||
|
||||
state = ctx->state;
|
||||
|
||||
for (p = ctx->response->pos; p < ctx->response->last; p++) {
|
||||
ch = *p;
|
||||
|
||||
#if 0
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"s:%d in:'%02Xd:%c'", state, ch, ch);
|
||||
#endif
|
||||
|
||||
switch (state) {
|
||||
|
||||
/* first char */
|
||||
case sw_start:
|
||||
|
||||
switch (ch) {
|
||||
case CR:
|
||||
ctx->header_end = p;
|
||||
state = sw_header_almost_done;
|
||||
break;
|
||||
case LF:
|
||||
ctx->header_end = p;
|
||||
goto header_done;
|
||||
default:
|
||||
state = sw_name;
|
||||
ctx->header_name_start = p;
|
||||
|
||||
c = (u_char) (ch | 0x20);
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
/* header name */
|
||||
case sw_name:
|
||||
c = (u_char) (ch | 0x20);
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch == ':') {
|
||||
ctx->header_name_end = p;
|
||||
state = sw_space_before_value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch == '-') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch == CR) {
|
||||
ctx->header_name_end = p;
|
||||
ctx->header_start = p;
|
||||
ctx->header_end = p;
|
||||
state = sw_almost_done;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch == LF) {
|
||||
ctx->header_name_end = p;
|
||||
ctx->header_start = p;
|
||||
ctx->header_end = p;
|
||||
goto done;
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
|
||||
/* space* before header value */
|
||||
case sw_space_before_value:
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
break;
|
||||
case CR:
|
||||
ctx->header_start = p;
|
||||
ctx->header_end = p;
|
||||
state = sw_almost_done;
|
||||
break;
|
||||
case LF:
|
||||
ctx->header_start = p;
|
||||
ctx->header_end = p;
|
||||
goto done;
|
||||
default:
|
||||
ctx->header_start = p;
|
||||
state = sw_value;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* header value */
|
||||
case sw_value:
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
ctx->header_end = p;
|
||||
state = sw_space_after_value;
|
||||
break;
|
||||
case CR:
|
||||
ctx->header_end = p;
|
||||
state = sw_almost_done;
|
||||
break;
|
||||
case LF:
|
||||
ctx->header_end = p;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
/* space* before end of header line */
|
||||
case sw_space_after_value:
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
break;
|
||||
case CR:
|
||||
state = sw_almost_done;
|
||||
break;
|
||||
case LF:
|
||||
goto done;
|
||||
default:
|
||||
state = sw_value;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* end of header line */
|
||||
case sw_almost_done:
|
||||
switch (ch) {
|
||||
case LF:
|
||||
goto done;
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* end of header */
|
||||
case sw_header_almost_done:
|
||||
switch (ch) {
|
||||
case LF:
|
||||
goto header_done;
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx->response->pos = p;
|
||||
ctx->state = state;
|
||||
|
||||
return NGX_AGAIN;
|
||||
|
||||
done:
|
||||
|
||||
ctx->response->pos = p + 1;
|
||||
ctx->state = sw_start;
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
header_done:
|
||||
|
||||
ctx->response->pos = p + 1;
|
||||
ctx->state = sw_start;
|
||||
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_process_body(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp process body");
|
||||
|
||||
if (ctx->done) {
|
||||
ctx->handler(ctx);
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_verify(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
int n;
|
||||
size_t len;
|
||||
X509_STORE *store;
|
||||
const u_char *p;
|
||||
OCSP_CERTID *id;
|
||||
OCSP_RESPONSE *ocsp;
|
||||
OCSP_BASICRESP *basic;
|
||||
ASN1_GENERALIZEDTIME *thisupdate, *nextupdate;
|
||||
|
||||
ocsp = NULL;
|
||||
basic = NULL;
|
||||
id = NULL;
|
||||
|
||||
if (ctx->code != 200) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* check the response */
|
||||
|
||||
len = ctx->response->last - ctx->response->pos;
|
||||
p = ctx->response->pos;
|
||||
|
||||
ocsp = d2i_OCSP_RESPONSE(NULL, &p, len);
|
||||
if (ocsp == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"d2i_OCSP_RESPONSE() failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
n = OCSP_response_status(ocsp);
|
||||
|
||||
if (n != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
|
||||
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"OCSP response not successful (%d: %s)",
|
||||
n, OCSP_response_status_str(n));
|
||||
goto error;
|
||||
}
|
||||
|
||||
basic = OCSP_response_get1_basic(ocsp);
|
||||
if (basic == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"OCSP_response_get1_basic() failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
|
||||
if (store == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
|
||||
"SSL_CTX_get_cert_store() failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (OCSP_basic_verify(basic, ctx->chain, store, ctx->flags) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"OCSP_basic_verify() failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
id = OCSP_cert_to_id(NULL, ctx->cert, ctx->issuer);
|
||||
if (id == NULL) {
|
||||
ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
|
||||
"OCSP_cert_to_id() failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (OCSP_resp_find_status(basic, id, &ctx->status, NULL, NULL,
|
||||
&thisupdate, &nextupdate)
|
||||
!= 1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"certificate status not found in the OCSP response");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (OCSP_check_validity(thisupdate, nextupdate, 300, -1) != 1) {
|
||||
ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"OCSP_check_validity() failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (nextupdate) {
|
||||
ctx->valid = ngx_ssl_stapling_time(nextupdate);
|
||||
if (ctx->valid == (time_t) NGX_ERROR) {
|
||||
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
||||
"invalid nextUpdate time in certificate status");
|
||||
goto error;
|
||||
}
|
||||
|
||||
} else {
|
||||
ctx->valid = NGX_MAX_TIME_T_VALUE;
|
||||
}
|
||||
|
||||
OCSP_CERTID_free(id);
|
||||
OCSP_BASICRESP_free(basic);
|
||||
OCSP_RESPONSE_free(ocsp);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp response, %s, %uz",
|
||||
OCSP_cert_status_str(ctx->status), len);
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
error:
|
||||
|
||||
if (id) {
|
||||
OCSP_CERTID_free(id);
|
||||
}
|
||||
|
||||
if (basic) {
|
||||
OCSP_BASICRESP_free(basic);
|
||||
}
|
||||
|
||||
if (ocsp) {
|
||||
OCSP_RESPONSE_free(ocsp);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data)
|
||||
{
|
||||
size_t len;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_ssl_ocsp_cache_t *cache;
|
||||
|
||||
if (data) {
|
||||
shm_zone->data = data;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
|
||||
if (shm_zone->shm.exists) {
|
||||
shm_zone->data = shpool->data;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
cache = ngx_slab_alloc(shpool, sizeof(ngx_ssl_ocsp_cache_t));
|
||||
if (cache == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
shpool->data = cache;
|
||||
shm_zone->data = cache;
|
||||
|
||||
ngx_rbtree_init(&cache->rbtree, &cache->sentinel,
|
||||
ngx_str_rbtree_insert_value);
|
||||
|
||||
ngx_queue_init(&cache->expire_queue);
|
||||
|
||||
len = sizeof(" in OCSP cache \"\"") + shm_zone->shm.name.len;
|
||||
|
||||
shpool->log_ctx = ngx_slab_alloc(shpool, len);
|
||||
if (shpool->log_ctx == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_sprintf(shpool->log_ctx, " in OCSP cache \"%V\"%Z",
|
||||
&shm_zone->shm.name);
|
||||
|
||||
shpool->log_nomem = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_cache_lookup(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
uint32_t hash;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_ssl_ocsp_cache_t *cache;
|
||||
ngx_ssl_ocsp_cache_node_t *node;
|
||||
|
||||
shm_zone = ctx->shm_zone;
|
||||
|
||||
if (shm_zone == NULL) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
if (ngx_ssl_ocsp_create_key(ctx) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp cache lookup");
|
||||
|
||||
cache = shm_zone->data;
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
hash = ngx_hash_key(ctx->key.data, ctx->key.len);
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
|
||||
node = (ngx_ssl_ocsp_cache_node_t *)
|
||||
ngx_str_rbtree_lookup(&cache->rbtree, &ctx->key, hash);
|
||||
|
||||
if (node) {
|
||||
if (node->valid > ngx_time()) {
|
||||
ctx->status = node->status;
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp cache hit, %s",
|
||||
OCSP_cert_status_str(ctx->status));
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_queue_remove(&node->queue);
|
||||
ngx_rbtree_delete(&cache->rbtree, &node->node.node);
|
||||
ngx_slab_free_locked(shpool, node);
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp cache expired");
|
||||
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp cache miss");
|
||||
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
time_t now, valid;
|
||||
uint32_t hash;
|
||||
ngx_queue_t *q;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_ssl_ocsp_cache_t *cache;
|
||||
ngx_ssl_ocsp_cache_node_t *node;
|
||||
|
||||
shm_zone = ctx->shm_zone;
|
||||
|
||||
if (shm_zone == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
valid = ctx->valid;
|
||||
|
||||
now = ngx_time();
|
||||
|
||||
if (valid < now) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (valid == NGX_MAX_TIME_T_VALUE) {
|
||||
valid = now + 3600;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp cache store, valid:%T", valid - now);
|
||||
|
||||
cache = shm_zone->data;
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
hash = ngx_hash_key(ctx->key.data, ctx->key.len);
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
|
||||
node = ngx_slab_calloc_locked(shpool,
|
||||
sizeof(ngx_ssl_ocsp_cache_node_t) + ctx->key.len);
|
||||
if (node == NULL) {
|
||||
|
||||
if (!ngx_queue_empty(&cache->expire_queue)) {
|
||||
q = ngx_queue_last(&cache->expire_queue);
|
||||
node = ngx_queue_data(q, ngx_ssl_ocsp_cache_node_t, queue);
|
||||
|
||||
ngx_rbtree_delete(&cache->rbtree, &node->node.node);
|
||||
ngx_queue_remove(q);
|
||||
ngx_slab_free_locked(shpool, node);
|
||||
|
||||
node = ngx_slab_alloc_locked(shpool,
|
||||
sizeof(ngx_ssl_ocsp_cache_node_t) + ctx->key.len);
|
||||
}
|
||||
|
||||
if (node == NULL) {
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
ngx_log_error(NGX_LOG_ALERT, ctx->log, 0,
|
||||
"could not allocate new entry%s", shpool->log_ctx);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
node->node.str.len = ctx->key.len;
|
||||
node->node.str.data = (u_char *) node + sizeof(ngx_ssl_ocsp_cache_node_t);
|
||||
ngx_memcpy(node->node.str.data, ctx->key.data, ctx->key.len);
|
||||
node->node.node.key = hash;
|
||||
node->status = ctx->status;
|
||||
node->valid = valid;
|
||||
|
||||
ngx_rbtree_insert(&cache->rbtree, &node->node.node);
|
||||
ngx_queue_insert_head(&cache->expire_queue, &node->queue);
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx)
|
||||
{
|
||||
u_char *p;
|
||||
X509_NAME *name;
|
||||
ASN1_INTEGER *serial;
|
||||
|
||||
p = ngx_pnalloc(ctx->pool, 60);
|
||||
if (p == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ctx->key.data = p;
|
||||
ctx->key.len = 60;
|
||||
|
||||
name = X509_get_subject_name(ctx->issuer);
|
||||
if (X509_NAME_digest(name, EVP_sha1(), p, NULL) == 0) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
p += 20;
|
||||
|
||||
if (X509_pubkey_digest(ctx->issuer, EVP_sha1(), p, NULL) == 0) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
p += 20;
|
||||
|
||||
serial = X509_get_serialNumber(ctx->cert);
|
||||
if (serial->length > 20) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
p = ngx_cpymem(p, serial->data, serial->length);
|
||||
ngx_memzero(p, 20 - serial->length);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
||||
"ssl ocsp key %xV", &ctx->key);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static u_char *
|
||||
ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len)
|
||||
{
|
||||
u_char *p;
|
||||
ngx_ssl_ocsp_ctx_t *ctx;
|
||||
|
||||
p = buf;
|
||||
|
||||
if (log->action) {
|
||||
p = ngx_snprintf(buf, len, " while %s", log->action);
|
||||
len -= p - buf;
|
||||
buf = p;
|
||||
}
|
||||
|
||||
ctx = log->data;
|
||||
|
||||
if (ctx) {
|
||||
p = ngx_snprintf(buf, len, ", responder: %V", &ctx->host);
|
||||
len -= p - buf;
|
||||
buf = p;
|
||||
}
|
||||
|
||||
if (ctx && ctx->peer.name) {
|
||||
p = ngx_snprintf(buf, len, ", peer: %V", ctx->peer.name);
|
||||
len -= p - buf;
|
||||
buf = p;
|
||||
}
|
||||
|
||||
if (ctx && ctx->name) {
|
||||
p = ngx_snprintf(buf, len, ", certificate: \"%s\"", ctx->name);
|
||||
len -= p - buf;
|
||||
buf = p;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file,
|
||||
ngx_str_t *responder, ngx_uint_t verify)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
|
||||
"\"ssl_stapling\" ignored, not supported");
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,
|
||||
ngx_uint_t depth, ngx_shm_zone_t *shm_zone)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_EMERG, ssl->log, 0,
|
||||
"\"ssl_ocsp\" is not supported on this platform");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
||||
ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ocsp_validate(ngx_connection_t *c)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_ssl_ocsp_cleanup(ngx_connection_t *c)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,1142 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
#include <ngx_event_pipe.h>
|
||||
|
||||
|
||||
static ngx_int_t ngx_event_pipe_read_upstream(ngx_event_pipe_t *p);
|
||||
static ngx_int_t ngx_event_pipe_write_to_downstream(ngx_event_pipe_t *p);
|
||||
|
||||
static ngx_int_t ngx_event_pipe_write_chain_to_temp_file(ngx_event_pipe_t *p);
|
||||
static ngx_inline void ngx_event_pipe_remove_shadow_links(ngx_buf_t *buf);
|
||||
static ngx_int_t ngx_event_pipe_drain_chains(ngx_event_pipe_t *p);
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_uint_t flags;
|
||||
ngx_event_t *rev, *wev;
|
||||
|
||||
for ( ;; ) {
|
||||
if (do_write) {
|
||||
p->log->action = "sending to client";
|
||||
|
||||
rc = ngx_event_pipe_write_to_downstream(p);
|
||||
|
||||
if (rc == NGX_ABORT) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
if (rc == NGX_BUSY) {
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
p->read = 0;
|
||||
p->upstream_blocked = 0;
|
||||
|
||||
p->log->action = "reading upstream";
|
||||
|
||||
if (ngx_event_pipe_read_upstream(p) == NGX_ABORT) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
if (!p->read && !p->upstream_blocked) {
|
||||
break;
|
||||
}
|
||||
|
||||
do_write = 1;
|
||||
}
|
||||
|
||||
if (p->upstream->fd != (ngx_socket_t) -1) {
|
||||
rev = p->upstream->read;
|
||||
|
||||
flags = (rev->eof || rev->error) ? NGX_CLOSE_EVENT : 0;
|
||||
|
||||
if (ngx_handle_read_event(rev, flags) != NGX_OK) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
if (!rev->delayed) {
|
||||
if (rev->active && !rev->ready) {
|
||||
ngx_add_timer(rev, p->read_timeout);
|
||||
|
||||
} else if (rev->timer_set) {
|
||||
ngx_del_timer(rev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p->downstream->fd != (ngx_socket_t) -1
|
||||
&& p->downstream->data == p->output_ctx)
|
||||
{
|
||||
wev = p->downstream->write;
|
||||
if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
if (!wev->delayed) {
|
||||
if (wev->active && !wev->ready) {
|
||||
ngx_add_timer(wev, p->send_timeout);
|
||||
|
||||
} else if (wev->timer_set) {
|
||||
ngx_del_timer(wev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_event_pipe_read_upstream(ngx_event_pipe_t *p)
|
||||
{
|
||||
off_t limit;
|
||||
ssize_t n, size;
|
||||
ngx_int_t rc;
|
||||
ngx_buf_t *b;
|
||||
ngx_msec_t delay;
|
||||
ngx_chain_t *chain, *cl, *ln;
|
||||
|
||||
if (p->upstream_eof || p->upstream_error || p->upstream_done) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#if (NGX_THREADS)
|
||||
|
||||
if (p->aio) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe read upstream: aio");
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
if (p->writing) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe read upstream: writing");
|
||||
|
||||
rc = ngx_event_pipe_write_chain_to_temp_file(p);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe read upstream: %d", p->upstream->read->ready);
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
if (p->upstream_eof || p->upstream_error || p->upstream_done) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (p->preread_bufs == NULL && !p->upstream->read->ready) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (p->preread_bufs) {
|
||||
|
||||
/* use the pre-read bufs if they exist */
|
||||
|
||||
chain = p->preread_bufs;
|
||||
p->preread_bufs = NULL;
|
||||
n = p->preread_size;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe preread: %z", n);
|
||||
|
||||
if (n) {
|
||||
p->read = 1;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
#if (NGX_HAVE_KQUEUE)
|
||||
|
||||
/*
|
||||
* kqueue notifies about the end of file or a pending error.
|
||||
* This test allows not to allocate a buf on these conditions
|
||||
* and not to call c->recv_chain().
|
||||
*/
|
||||
|
||||
if (p->upstream->read->available == 0
|
||||
&& p->upstream->read->pending_eof
|
||||
#if (NGX_SSL)
|
||||
&& !p->upstream->ssl
|
||||
#endif
|
||||
)
|
||||
{
|
||||
p->upstream->read->ready = 0;
|
||||
p->upstream->read->eof = 1;
|
||||
p->upstream_eof = 1;
|
||||
p->read = 1;
|
||||
|
||||
if (p->upstream->read->kq_errno) {
|
||||
p->upstream->read->error = 1;
|
||||
p->upstream_error = 1;
|
||||
p->upstream_eof = 0;
|
||||
|
||||
ngx_log_error(NGX_LOG_ERR, p->log,
|
||||
p->upstream->read->kq_errno,
|
||||
"kevent() reported that upstream "
|
||||
"closed connection");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (p->limit_rate) {
|
||||
if (p->upstream->read->delayed) {
|
||||
break;
|
||||
}
|
||||
|
||||
limit = (off_t) p->limit_rate * (ngx_time() - p->start_sec + 1)
|
||||
- p->read_length;
|
||||
|
||||
if (limit <= 0) {
|
||||
p->upstream->read->delayed = 1;
|
||||
delay = (ngx_msec_t) (- limit * 1000 / p->limit_rate + 1);
|
||||
ngx_add_timer(p->upstream->read, delay);
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
limit = 0;
|
||||
}
|
||||
|
||||
if (p->free_raw_bufs) {
|
||||
|
||||
/* use the free bufs if they exist */
|
||||
|
||||
chain = p->free_raw_bufs;
|
||||
if (p->single_buf) {
|
||||
p->free_raw_bufs = p->free_raw_bufs->next;
|
||||
chain->next = NULL;
|
||||
} else {
|
||||
p->free_raw_bufs = NULL;
|
||||
}
|
||||
|
||||
} else if (p->allocated < p->bufs.num) {
|
||||
|
||||
/* allocate a new buf if it's still allowed */
|
||||
|
||||
b = ngx_create_temp_buf(p->pool, p->bufs.size);
|
||||
if (b == NULL) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
p->allocated++;
|
||||
|
||||
chain = ngx_alloc_chain_link(p->pool);
|
||||
if (chain == NULL) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
chain->buf = b;
|
||||
chain->next = NULL;
|
||||
|
||||
} else if (!p->cacheable
|
||||
&& p->downstream->data == p->output_ctx
|
||||
&& p->downstream->write->ready
|
||||
&& !p->downstream->write->delayed)
|
||||
{
|
||||
/*
|
||||
* if the bufs are not needed to be saved in a cache and
|
||||
* a downstream is ready then write the bufs to a downstream
|
||||
*/
|
||||
|
||||
p->upstream_blocked = 1;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe downstream ready");
|
||||
|
||||
break;
|
||||
|
||||
} else if (p->cacheable
|
||||
|| p->temp_file->offset < p->max_temp_file_size)
|
||||
{
|
||||
|
||||
/*
|
||||
* if it is allowed, then save some bufs from p->in
|
||||
* to a temporary file, and add them to a p->out chain
|
||||
*/
|
||||
|
||||
rc = ngx_event_pipe_write_chain_to_temp_file(p);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe temp offset: %O", p->temp_file->offset);
|
||||
|
||||
if (rc == NGX_BUSY) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
chain = p->free_raw_bufs;
|
||||
if (p->single_buf) {
|
||||
p->free_raw_bufs = p->free_raw_bufs->next;
|
||||
chain->next = NULL;
|
||||
} else {
|
||||
p->free_raw_bufs = NULL;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/* there are no bufs to read in */
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"no pipe bufs to read in");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
n = p->upstream->recv_chain(p->upstream, chain, limit);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe recv chain: %z", n);
|
||||
|
||||
if (p->free_raw_bufs) {
|
||||
chain->next = p->free_raw_bufs;
|
||||
}
|
||||
p->free_raw_bufs = chain;
|
||||
|
||||
if (n == NGX_ERROR) {
|
||||
p->upstream_error = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (n == NGX_AGAIN) {
|
||||
if (p->single_buf) {
|
||||
ngx_event_pipe_remove_shadow_links(chain->buf);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
p->read = 1;
|
||||
|
||||
if (n == 0) {
|
||||
p->upstream_eof = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delay = p->limit_rate ? (ngx_msec_t) n * 1000 / p->limit_rate : 0;
|
||||
|
||||
p->read_length += n;
|
||||
cl = chain;
|
||||
p->free_raw_bufs = NULL;
|
||||
|
||||
while (cl && n > 0) {
|
||||
|
||||
ngx_event_pipe_remove_shadow_links(cl->buf);
|
||||
|
||||
size = cl->buf->end - cl->buf->last;
|
||||
|
||||
if (n >= size) {
|
||||
cl->buf->last = cl->buf->end;
|
||||
|
||||
/* STUB */ cl->buf->num = p->num++;
|
||||
|
||||
if (p->input_filter(p, cl->buf) == NGX_ERROR) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
n -= size;
|
||||
ln = cl;
|
||||
cl = cl->next;
|
||||
ngx_free_chain(p->pool, ln);
|
||||
|
||||
} else {
|
||||
cl->buf->last += n;
|
||||
n = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cl) {
|
||||
for (ln = cl; ln->next; ln = ln->next) { /* void */ }
|
||||
|
||||
ln->next = p->free_raw_bufs;
|
||||
p->free_raw_bufs = cl;
|
||||
}
|
||||
|
||||
if (delay > 0) {
|
||||
p->upstream->read->delayed = 1;
|
||||
ngx_add_timer(p->upstream->read, delay);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
|
||||
for (cl = p->busy; cl; cl = cl->next) {
|
||||
ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe buf busy s:%d t:%d f:%d "
|
||||
"%p, pos %p, size: %z "
|
||||
"file: %O, size: %O",
|
||||
(cl->buf->shadow ? 1 : 0),
|
||||
cl->buf->temporary, cl->buf->in_file,
|
||||
cl->buf->start, cl->buf->pos,
|
||||
cl->buf->last - cl->buf->pos,
|
||||
cl->buf->file_pos,
|
||||
cl->buf->file_last - cl->buf->file_pos);
|
||||
}
|
||||
|
||||
for (cl = p->out; cl; cl = cl->next) {
|
||||
ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe buf out s:%d t:%d f:%d "
|
||||
"%p, pos %p, size: %z "
|
||||
"file: %O, size: %O",
|
||||
(cl->buf->shadow ? 1 : 0),
|
||||
cl->buf->temporary, cl->buf->in_file,
|
||||
cl->buf->start, cl->buf->pos,
|
||||
cl->buf->last - cl->buf->pos,
|
||||
cl->buf->file_pos,
|
||||
cl->buf->file_last - cl->buf->file_pos);
|
||||
}
|
||||
|
||||
for (cl = p->in; cl; cl = cl->next) {
|
||||
ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe buf in s:%d t:%d f:%d "
|
||||
"%p, pos %p, size: %z "
|
||||
"file: %O, size: %O",
|
||||
(cl->buf->shadow ? 1 : 0),
|
||||
cl->buf->temporary, cl->buf->in_file,
|
||||
cl->buf->start, cl->buf->pos,
|
||||
cl->buf->last - cl->buf->pos,
|
||||
cl->buf->file_pos,
|
||||
cl->buf->file_last - cl->buf->file_pos);
|
||||
}
|
||||
|
||||
for (cl = p->free_raw_bufs; cl; cl = cl->next) {
|
||||
ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe buf free s:%d t:%d f:%d "
|
||||
"%p, pos %p, size: %z "
|
||||
"file: %O, size: %O",
|
||||
(cl->buf->shadow ? 1 : 0),
|
||||
cl->buf->temporary, cl->buf->in_file,
|
||||
cl->buf->start, cl->buf->pos,
|
||||
cl->buf->last - cl->buf->pos,
|
||||
cl->buf->file_pos,
|
||||
cl->buf->file_last - cl->buf->file_pos);
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe length: %O", p->length);
|
||||
|
||||
#endif
|
||||
|
||||
if (p->free_raw_bufs && p->length != -1) {
|
||||
cl = p->free_raw_bufs;
|
||||
|
||||
if (cl->buf->last - cl->buf->pos >= p->length) {
|
||||
|
||||
p->free_raw_bufs = cl->next;
|
||||
|
||||
/* STUB */ cl->buf->num = p->num++;
|
||||
|
||||
if (p->input_filter(p, cl->buf) == NGX_ERROR) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
ngx_free_chain(p->pool, cl);
|
||||
}
|
||||
}
|
||||
|
||||
if (p->length == 0) {
|
||||
p->upstream_done = 1;
|
||||
p->read = 1;
|
||||
}
|
||||
|
||||
if ((p->upstream_eof || p->upstream_error) && p->free_raw_bufs) {
|
||||
|
||||
/* STUB */ p->free_raw_bufs->buf->num = p->num++;
|
||||
|
||||
if (p->input_filter(p, p->free_raw_bufs->buf) == NGX_ERROR) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
p->free_raw_bufs = p->free_raw_bufs->next;
|
||||
|
||||
if (p->free_bufs && p->buf_to_file == NULL) {
|
||||
for (cl = p->free_raw_bufs; cl; cl = cl->next) {
|
||||
if (cl->buf->shadow == NULL) {
|
||||
ngx_pfree(p->pool, cl->buf->start);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p->cacheable && (p->in || p->buf_to_file)) {
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe write chain");
|
||||
|
||||
rc = ngx_event_pipe_write_chain_to_temp_file(p);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_event_pipe_write_to_downstream(ngx_event_pipe_t *p)
|
||||
{
|
||||
u_char *prev;
|
||||
size_t bsize;
|
||||
ngx_int_t rc;
|
||||
ngx_uint_t flush, flushed, prev_last_shadow;
|
||||
ngx_chain_t *out, **ll, *cl;
|
||||
ngx_connection_t *downstream;
|
||||
|
||||
downstream = p->downstream;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe write downstream: %d", downstream->write->ready);
|
||||
|
||||
#if (NGX_THREADS)
|
||||
|
||||
if (p->writing) {
|
||||
rc = ngx_event_pipe_write_chain_to_temp_file(p);
|
||||
|
||||
if (rc == NGX_ABORT) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
flushed = 0;
|
||||
|
||||
for ( ;; ) {
|
||||
if (p->downstream_error) {
|
||||
return ngx_event_pipe_drain_chains(p);
|
||||
}
|
||||
|
||||
if (p->upstream_eof || p->upstream_error || p->upstream_done) {
|
||||
|
||||
/* pass the p->out and p->in chains to the output filter */
|
||||
|
||||
for (cl = p->busy; cl; cl = cl->next) {
|
||||
cl->buf->recycled = 0;
|
||||
}
|
||||
|
||||
if (p->out) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe write downstream flush out");
|
||||
|
||||
for (cl = p->out; cl; cl = cl->next) {
|
||||
cl->buf->recycled = 0;
|
||||
}
|
||||
|
||||
rc = p->output_filter(p->output_ctx, p->out);
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
p->downstream_error = 1;
|
||||
return ngx_event_pipe_drain_chains(p);
|
||||
}
|
||||
|
||||
p->out = NULL;
|
||||
}
|
||||
|
||||
if (p->writing) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (p->in) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe write downstream flush in");
|
||||
|
||||
for (cl = p->in; cl; cl = cl->next) {
|
||||
cl->buf->recycled = 0;
|
||||
}
|
||||
|
||||
rc = p->output_filter(p->output_ctx, p->in);
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
p->downstream_error = 1;
|
||||
return ngx_event_pipe_drain_chains(p);
|
||||
}
|
||||
|
||||
p->in = NULL;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe write downstream done");
|
||||
|
||||
/* TODO: free unused bufs */
|
||||
|
||||
p->downstream_done = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (downstream->data != p->output_ctx
|
||||
|| !downstream->write->ready
|
||||
|| downstream->write->delayed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
/* bsize is the size of the busy recycled bufs */
|
||||
|
||||
prev = NULL;
|
||||
bsize = 0;
|
||||
|
||||
for (cl = p->busy; cl; cl = cl->next) {
|
||||
|
||||
if (cl->buf->recycled) {
|
||||
if (prev == cl->buf->start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bsize += cl->buf->end - cl->buf->start;
|
||||
prev = cl->buf->start;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe write busy: %uz", bsize);
|
||||
|
||||
out = NULL;
|
||||
|
||||
if (bsize >= (size_t) p->busy_size) {
|
||||
flush = 1;
|
||||
goto flush;
|
||||
}
|
||||
|
||||
flush = 0;
|
||||
ll = NULL;
|
||||
prev_last_shadow = 1;
|
||||
|
||||
for ( ;; ) {
|
||||
if (p->out) {
|
||||
cl = p->out;
|
||||
|
||||
if (cl->buf->recycled) {
|
||||
ngx_log_error(NGX_LOG_ALERT, p->log, 0,
|
||||
"recycled buffer in pipe out chain");
|
||||
}
|
||||
|
||||
p->out = p->out->next;
|
||||
|
||||
} else if (!p->cacheable && !p->writing && p->in) {
|
||||
cl = p->in;
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe write buf ls:%d %p %z",
|
||||
cl->buf->last_shadow,
|
||||
cl->buf->pos,
|
||||
cl->buf->last - cl->buf->pos);
|
||||
|
||||
if (cl->buf->recycled && prev_last_shadow) {
|
||||
if (bsize + cl->buf->end - cl->buf->start > p->busy_size) {
|
||||
flush = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
bsize += cl->buf->end - cl->buf->start;
|
||||
}
|
||||
|
||||
prev_last_shadow = cl->buf->last_shadow;
|
||||
|
||||
p->in = p->in->next;
|
||||
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
cl->next = NULL;
|
||||
|
||||
if (out) {
|
||||
*ll = cl;
|
||||
} else {
|
||||
out = cl;
|
||||
}
|
||||
ll = &cl->next;
|
||||
}
|
||||
|
||||
flush:
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe write: out:%p, f:%ui", out, flush);
|
||||
|
||||
if (out == NULL) {
|
||||
|
||||
if (!flush) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* a workaround for AIO */
|
||||
if (flushed++ > 10) {
|
||||
return NGX_BUSY;
|
||||
}
|
||||
}
|
||||
|
||||
rc = p->output_filter(p->output_ctx, out);
|
||||
|
||||
ngx_chain_update_chains(p->pool, &p->free, &p->busy, &out, p->tag);
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
p->downstream_error = 1;
|
||||
return ngx_event_pipe_drain_chains(p);
|
||||
}
|
||||
|
||||
for (cl = p->free; cl; cl = cl->next) {
|
||||
|
||||
if (cl->buf->temp_file) {
|
||||
if (p->cacheable || !p->cyclic_temp_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* reset p->temp_offset if all bufs had been sent */
|
||||
|
||||
if (cl->buf->file_last == p->temp_file->offset) {
|
||||
p->temp_file->offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: free buf if p->free_bufs && upstream done */
|
||||
|
||||
/* add the free shadow raw buf to p->free_raw_bufs */
|
||||
|
||||
if (cl->buf->last_shadow) {
|
||||
if (ngx_event_pipe_add_free_buf(p, cl->buf->shadow) != NGX_OK) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
cl->buf->last_shadow = 0;
|
||||
}
|
||||
|
||||
cl->buf->shadow = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_event_pipe_write_chain_to_temp_file(ngx_event_pipe_t *p)
|
||||
{
|
||||
ssize_t size, bsize, n;
|
||||
ngx_buf_t *b;
|
||||
ngx_uint_t prev_last_shadow;
|
||||
ngx_chain_t *cl, *tl, *next, *out, **ll, **last_out, **last_free;
|
||||
|
||||
#if (NGX_THREADS)
|
||||
|
||||
if (p->writing) {
|
||||
|
||||
if (p->aio) {
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
out = p->writing;
|
||||
p->writing = NULL;
|
||||
|
||||
n = ngx_write_chain_to_temp_file(p->temp_file, NULL);
|
||||
|
||||
if (n == NGX_ERROR) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (p->buf_to_file) {
|
||||
out = ngx_alloc_chain_link(p->pool);
|
||||
if (out == NULL) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
out->buf = p->buf_to_file;
|
||||
out->next = p->in;
|
||||
|
||||
} else {
|
||||
out = p->in;
|
||||
}
|
||||
|
||||
if (!p->cacheable) {
|
||||
|
||||
size = 0;
|
||||
cl = out;
|
||||
ll = NULL;
|
||||
prev_last_shadow = 1;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe offset: %O", p->temp_file->offset);
|
||||
|
||||
do {
|
||||
bsize = cl->buf->last - cl->buf->pos;
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"pipe buf ls:%d %p, pos %p, size: %z",
|
||||
cl->buf->last_shadow, cl->buf->start,
|
||||
cl->buf->pos, bsize);
|
||||
|
||||
if (prev_last_shadow
|
||||
&& ((size + bsize > p->temp_file_write_size)
|
||||
|| (p->temp_file->offset + size + bsize
|
||||
> p->max_temp_file_size)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
prev_last_shadow = cl->buf->last_shadow;
|
||||
|
||||
size += bsize;
|
||||
ll = &cl->next;
|
||||
cl = cl->next;
|
||||
|
||||
} while (cl);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "size: %z", size);
|
||||
|
||||
if (ll == NULL) {
|
||||
return NGX_BUSY;
|
||||
}
|
||||
|
||||
if (cl) {
|
||||
p->in = cl;
|
||||
*ll = NULL;
|
||||
|
||||
} else {
|
||||
p->in = NULL;
|
||||
p->last_in = &p->in;
|
||||
}
|
||||
|
||||
} else {
|
||||
p->in = NULL;
|
||||
p->last_in = &p->in;
|
||||
}
|
||||
|
||||
#if (NGX_THREADS)
|
||||
if (p->thread_handler) {
|
||||
p->temp_file->thread_write = 1;
|
||||
p->temp_file->file.thread_task = p->thread_task;
|
||||
p->temp_file->file.thread_handler = p->thread_handler;
|
||||
p->temp_file->file.thread_ctx = p->thread_ctx;
|
||||
}
|
||||
#endif
|
||||
|
||||
n = ngx_write_chain_to_temp_file(p->temp_file, out);
|
||||
|
||||
if (n == NGX_ERROR) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
#if (NGX_THREADS)
|
||||
|
||||
if (n == NGX_AGAIN) {
|
||||
p->writing = out;
|
||||
p->thread_task = p->temp_file->file.thread_task;
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
#endif
|
||||
|
||||
if (p->buf_to_file) {
|
||||
p->temp_file->offset = p->buf_to_file->last - p->buf_to_file->pos;
|
||||
n -= p->buf_to_file->last - p->buf_to_file->pos;
|
||||
p->buf_to_file = NULL;
|
||||
out = out->next;
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
/* update previous buffer or add new buffer */
|
||||
|
||||
if (p->out) {
|
||||
for (cl = p->out; cl->next; cl = cl->next) { /* void */ }
|
||||
|
||||
b = cl->buf;
|
||||
|
||||
if (b->file_last == p->temp_file->offset) {
|
||||
p->temp_file->offset += n;
|
||||
b->file_last = p->temp_file->offset;
|
||||
goto free;
|
||||
}
|
||||
|
||||
last_out = &cl->next;
|
||||
|
||||
} else {
|
||||
last_out = &p->out;
|
||||
}
|
||||
|
||||
cl = ngx_chain_get_free_buf(p->pool, &p->free);
|
||||
if (cl == NULL) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
b = cl->buf;
|
||||
|
||||
ngx_memzero(b, sizeof(ngx_buf_t));
|
||||
|
||||
b->tag = p->tag;
|
||||
|
||||
b->file = &p->temp_file->file;
|
||||
b->file_pos = p->temp_file->offset;
|
||||
p->temp_file->offset += n;
|
||||
b->file_last = p->temp_file->offset;
|
||||
|
||||
b->in_file = 1;
|
||||
b->temp_file = 1;
|
||||
|
||||
*last_out = cl;
|
||||
}
|
||||
|
||||
free:
|
||||
|
||||
for (last_free = &p->free_raw_bufs;
|
||||
*last_free != NULL;
|
||||
last_free = &(*last_free)->next)
|
||||
{
|
||||
/* void */
|
||||
}
|
||||
|
||||
for (cl = out; cl; cl = next) {
|
||||
next = cl->next;
|
||||
|
||||
cl->next = p->free;
|
||||
p->free = cl;
|
||||
|
||||
b = cl->buf;
|
||||
|
||||
if (b->last_shadow) {
|
||||
|
||||
tl = ngx_alloc_chain_link(p->pool);
|
||||
if (tl == NULL) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
tl->buf = b->shadow;
|
||||
tl->next = NULL;
|
||||
|
||||
*last_free = tl;
|
||||
last_free = &tl->next;
|
||||
|
||||
b->shadow->pos = b->shadow->start;
|
||||
b->shadow->last = b->shadow->start;
|
||||
|
||||
ngx_event_pipe_remove_shadow_links(b->shadow);
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
/* the copy input filter */
|
||||
|
||||
ngx_int_t
|
||||
ngx_event_pipe_copy_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
ngx_chain_t *cl;
|
||||
|
||||
if (buf->pos == buf->last) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (p->upstream_done) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
|
||||
"input data after close");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (p->length == 0) {
|
||||
p->upstream_done = 1;
|
||||
|
||||
ngx_log_error(NGX_LOG_WARN, p->log, 0,
|
||||
"upstream sent more data than specified in "
|
||||
"\"Content-Length\" header");
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
cl = ngx_chain_get_free_buf(p->pool, &p->free);
|
||||
if (cl == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
b = cl->buf;
|
||||
|
||||
ngx_memcpy(b, buf, sizeof(ngx_buf_t));
|
||||
b->shadow = buf;
|
||||
b->tag = p->tag;
|
||||
b->last_shadow = 1;
|
||||
b->recycled = 1;
|
||||
buf->shadow = b;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "input buf #%d", b->num);
|
||||
|
||||
if (p->in) {
|
||||
*p->last_in = cl;
|
||||
} else {
|
||||
p->in = cl;
|
||||
}
|
||||
p->last_in = &cl->next;
|
||||
|
||||
if (p->length == -1) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (b->last - b->pos > p->length) {
|
||||
|
||||
ngx_log_error(NGX_LOG_WARN, p->log, 0,
|
||||
"upstream sent more data than specified in "
|
||||
"\"Content-Length\" header");
|
||||
|
||||
b->last = b->pos + p->length;
|
||||
p->upstream_done = 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
p->length -= b->last - b->pos;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline void
|
||||
ngx_event_pipe_remove_shadow_links(ngx_buf_t *buf)
|
||||
{
|
||||
ngx_buf_t *b, *next;
|
||||
|
||||
b = buf->shadow;
|
||||
|
||||
if (b == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (!b->last_shadow) {
|
||||
next = b->shadow;
|
||||
|
||||
b->temporary = 0;
|
||||
b->recycled = 0;
|
||||
|
||||
b->shadow = NULL;
|
||||
b = next;
|
||||
}
|
||||
|
||||
b->temporary = 0;
|
||||
b->recycled = 0;
|
||||
b->last_shadow = 0;
|
||||
|
||||
b->shadow = NULL;
|
||||
|
||||
buf->shadow = NULL;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_event_pipe_add_free_buf(ngx_event_pipe_t *p, ngx_buf_t *b)
|
||||
{
|
||||
ngx_chain_t *cl;
|
||||
|
||||
cl = ngx_alloc_chain_link(p->pool);
|
||||
if (cl == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (p->buf_to_file && b->start == p->buf_to_file->start) {
|
||||
b->pos = p->buf_to_file->last;
|
||||
b->last = p->buf_to_file->last;
|
||||
|
||||
} else {
|
||||
b->pos = b->start;
|
||||
b->last = b->start;
|
||||
}
|
||||
|
||||
b->shadow = NULL;
|
||||
|
||||
cl->buf = b;
|
||||
|
||||
if (p->free_raw_bufs == NULL) {
|
||||
p->free_raw_bufs = cl;
|
||||
cl->next = NULL;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (p->free_raw_bufs->buf->pos == p->free_raw_bufs->buf->last) {
|
||||
|
||||
/* add the free buf to the list start */
|
||||
|
||||
cl->next = p->free_raw_bufs;
|
||||
p->free_raw_bufs = cl;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* the first free buf is partially filled, thus add the free buf after it */
|
||||
|
||||
cl->next = p->free_raw_bufs->next;
|
||||
p->free_raw_bufs->next = cl;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_event_pipe_drain_chains(ngx_event_pipe_t *p)
|
||||
{
|
||||
ngx_chain_t *cl, *tl;
|
||||
|
||||
for ( ;; ) {
|
||||
if (p->busy) {
|
||||
cl = p->busy;
|
||||
p->busy = NULL;
|
||||
|
||||
} else if (p->out) {
|
||||
cl = p->out;
|
||||
p->out = NULL;
|
||||
|
||||
} else if (p->in) {
|
||||
cl = p->in;
|
||||
p->in = NULL;
|
||||
|
||||
} else {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
while (cl) {
|
||||
if (cl->buf->last_shadow) {
|
||||
if (ngx_event_pipe_add_free_buf(p, cl->buf->shadow) != NGX_OK) {
|
||||
return NGX_ABORT;
|
||||
}
|
||||
|
||||
cl->buf->last_shadow = 0;
|
||||
}
|
||||
|
||||
cl->buf->shadow = NULL;
|
||||
tl = cl->next;
|
||||
cl->next = p->free;
|
||||
p->free = cl;
|
||||
cl = tl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_EVENT_PIPE_H_INCLUDED_
|
||||
#define _NGX_EVENT_PIPE_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
typedef struct ngx_event_pipe_s ngx_event_pipe_t;
|
||||
|
||||
typedef ngx_int_t (*ngx_event_pipe_input_filter_pt)(ngx_event_pipe_t *p,
|
||||
ngx_buf_t *buf);
|
||||
typedef ngx_int_t (*ngx_event_pipe_output_filter_pt)(void *data,
|
||||
ngx_chain_t *chain);
|
||||
|
||||
|
||||
struct ngx_event_pipe_s {
|
||||
ngx_connection_t *upstream;
|
||||
ngx_connection_t *downstream;
|
||||
|
||||
ngx_chain_t *free_raw_bufs;
|
||||
ngx_chain_t *in;
|
||||
ngx_chain_t **last_in;
|
||||
|
||||
ngx_chain_t *writing;
|
||||
|
||||
ngx_chain_t *out;
|
||||
ngx_chain_t *free;
|
||||
ngx_chain_t *busy;
|
||||
|
||||
/*
|
||||
* the input filter i.e. that moves HTTP/1.1 chunks
|
||||
* from the raw bufs to an incoming chain
|
||||
*/
|
||||
|
||||
ngx_event_pipe_input_filter_pt input_filter;
|
||||
void *input_ctx;
|
||||
|
||||
ngx_event_pipe_output_filter_pt output_filter;
|
||||
void *output_ctx;
|
||||
|
||||
#if (NGX_THREADS || NGX_COMPAT)
|
||||
ngx_int_t (*thread_handler)(ngx_thread_task_t *task,
|
||||
ngx_file_t *file);
|
||||
void *thread_ctx;
|
||||
ngx_thread_task_t *thread_task;
|
||||
#endif
|
||||
|
||||
unsigned read:1;
|
||||
unsigned cacheable:1;
|
||||
unsigned single_buf:1;
|
||||
unsigned free_bufs:1;
|
||||
unsigned upstream_done:1;
|
||||
unsigned upstream_error:1;
|
||||
unsigned upstream_eof:1;
|
||||
unsigned upstream_blocked:1;
|
||||
unsigned downstream_done:1;
|
||||
unsigned downstream_error:1;
|
||||
unsigned cyclic_temp_file:1;
|
||||
unsigned aio:1;
|
||||
|
||||
ngx_int_t allocated;
|
||||
ngx_bufs_t bufs;
|
||||
ngx_buf_tag_t tag;
|
||||
|
||||
ssize_t busy_size;
|
||||
|
||||
off_t read_length;
|
||||
off_t length;
|
||||
|
||||
off_t max_temp_file_size;
|
||||
ssize_t temp_file_write_size;
|
||||
|
||||
ngx_msec_t read_timeout;
|
||||
ngx_msec_t send_timeout;
|
||||
ssize_t send_lowat;
|
||||
|
||||
ngx_pool_t *pool;
|
||||
ngx_log_t *log;
|
||||
|
||||
ngx_chain_t *preread_bufs;
|
||||
size_t preread_size;
|
||||
ngx_buf_t *buf_to_file;
|
||||
|
||||
size_t limit_rate;
|
||||
time_t start_sec;
|
||||
|
||||
ngx_temp_file_t *temp_file;
|
||||
|
||||
/* STUB */ int num;
|
||||
};
|
||||
|
||||
|
||||
ngx_int_t ngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write);
|
||||
ngx_int_t ngx_event_pipe_copy_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf);
|
||||
ngx_int_t ngx_event_pipe_add_free_buf(ngx_event_pipe_t *p, ngx_buf_t *b);
|
||||
|
||||
|
||||
#endif /* _NGX_EVENT_PIPE_H_INCLUDED_ */
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
ngx_queue_t ngx_posted_accept_events;
|
||||
ngx_queue_t ngx_posted_next_events;
|
||||
ngx_queue_t ngx_posted_events;
|
||||
|
||||
|
||||
void
|
||||
ngx_event_process_posted(ngx_cycle_t *cycle, ngx_queue_t *posted)
|
||||
{
|
||||
ngx_queue_t *q;
|
||||
ngx_event_t *ev;
|
||||
|
||||
while (!ngx_queue_empty(posted)) {
|
||||
|
||||
q = ngx_queue_head(posted);
|
||||
ev = ngx_queue_data(q, ngx_event_t, queue);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"posted event %p", ev);
|
||||
|
||||
ngx_delete_posted_event(ev);
|
||||
|
||||
ev->handler(ev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_event_move_posted_next(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_queue_t *q;
|
||||
ngx_event_t *ev;
|
||||
|
||||
for (q = ngx_queue_head(&ngx_posted_next_events);
|
||||
q != ngx_queue_sentinel(&ngx_posted_next_events);
|
||||
q = ngx_queue_next(q))
|
||||
{
|
||||
ev = ngx_queue_data(q, ngx_event_t, queue);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
|
||||
"posted next event %p", ev);
|
||||
|
||||
ev->ready = 1;
|
||||
ev->available = -1;
|
||||
}
|
||||
|
||||
ngx_queue_add(&ngx_posted_events, &ngx_posted_next_events);
|
||||
ngx_queue_init(&ngx_posted_next_events);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_EVENT_POSTED_H_INCLUDED_
|
||||
#define _NGX_EVENT_POSTED_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
#define ngx_post_event(ev, q) \
|
||||
\
|
||||
if (!(ev)->posted) { \
|
||||
(ev)->posted = 1; \
|
||||
ngx_queue_insert_tail(q, &(ev)->queue); \
|
||||
\
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_CORE, (ev)->log, 0, "post event %p", ev);\
|
||||
\
|
||||
} else { \
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_CORE, (ev)->log, 0, \
|
||||
"update posted event %p", ev); \
|
||||
}
|
||||
|
||||
|
||||
#define ngx_delete_posted_event(ev) \
|
||||
\
|
||||
(ev)->posted = 0; \
|
||||
ngx_queue_remove(&(ev)->queue); \
|
||||
\
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_CORE, (ev)->log, 0, \
|
||||
"delete posted event %p", ev);
|
||||
|
||||
|
||||
|
||||
void ngx_event_process_posted(ngx_cycle_t *cycle, ngx_queue_t *posted);
|
||||
void ngx_event_move_posted_next(ngx_cycle_t *cycle);
|
||||
|
||||
|
||||
extern ngx_queue_t ngx_posted_accept_events;
|
||||
extern ngx_queue_t ngx_posted_next_events;
|
||||
extern ngx_queue_t ngx_posted_events;
|
||||
|
||||
|
||||
#endif /* _NGX_EVENT_POSTED_H_INCLUDED_ */
|
||||
@@ -0,0 +1,126 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
ngx_rbtree_t ngx_event_timer_rbtree;
|
||||
static ngx_rbtree_node_t ngx_event_timer_sentinel;
|
||||
|
||||
/*
|
||||
* the event timer rbtree may contain the duplicate keys, however,
|
||||
* it should not be a problem, because we use the rbtree to find
|
||||
* a minimum timer value only
|
||||
*/
|
||||
|
||||
ngx_int_t
|
||||
ngx_event_timer_init(ngx_log_t *log)
|
||||
{
|
||||
ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,
|
||||
ngx_rbtree_insert_timer_value);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_msec_t
|
||||
ngx_event_find_timer(void)
|
||||
{
|
||||
ngx_msec_int_t timer;
|
||||
ngx_rbtree_node_t *node, *root, *sentinel;
|
||||
|
||||
if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel) {
|
||||
return NGX_TIMER_INFINITE;
|
||||
}
|
||||
|
||||
root = ngx_event_timer_rbtree.root;
|
||||
sentinel = ngx_event_timer_rbtree.sentinel;
|
||||
|
||||
node = ngx_rbtree_min(root, sentinel);
|
||||
|
||||
timer = (ngx_msec_int_t) (node->key - ngx_current_msec);
|
||||
|
||||
return (ngx_msec_t) (timer > 0 ? timer : 0);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_event_expire_timers(void)
|
||||
{
|
||||
ngx_event_t *ev;
|
||||
ngx_rbtree_node_t *node, *root, *sentinel;
|
||||
|
||||
sentinel = ngx_event_timer_rbtree.sentinel;
|
||||
|
||||
for ( ;; ) {
|
||||
root = ngx_event_timer_rbtree.root;
|
||||
|
||||
if (root == sentinel) {
|
||||
return;
|
||||
}
|
||||
|
||||
node = ngx_rbtree_min(root, sentinel);
|
||||
|
||||
/* node->key > ngx_current_msec */
|
||||
|
||||
if ((ngx_msec_int_t) (node->key - ngx_current_msec) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev = ngx_rbtree_data(node, ngx_event_t, timer);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"event timer del: %d: %M",
|
||||
ngx_event_ident(ev->data), ev->timer.key);
|
||||
|
||||
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
ev->timer.left = NULL;
|
||||
ev->timer.right = NULL;
|
||||
ev->timer.parent = NULL;
|
||||
#endif
|
||||
|
||||
ev->timer_set = 0;
|
||||
|
||||
ev->timedout = 1;
|
||||
|
||||
ev->handler(ev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_event_no_timers_left(void)
|
||||
{
|
||||
ngx_event_t *ev;
|
||||
ngx_rbtree_node_t *node, *root, *sentinel;
|
||||
|
||||
sentinel = ngx_event_timer_rbtree.sentinel;
|
||||
root = ngx_event_timer_rbtree.root;
|
||||
|
||||
if (root == sentinel) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
for (node = ngx_rbtree_min(root, sentinel);
|
||||
node;
|
||||
node = ngx_rbtree_next(&ngx_event_timer_rbtree, node))
|
||||
{
|
||||
ev = ngx_rbtree_data(node, ngx_event_t, timer);
|
||||
|
||||
if (!ev->cancelable) {
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
/* only cancelable timers left */
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_EVENT_TIMER_H_INCLUDED_
|
||||
#define _NGX_EVENT_TIMER_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
#define NGX_TIMER_INFINITE (ngx_msec_t) -1
|
||||
|
||||
#define NGX_TIMER_LAZY_DELAY 300
|
||||
|
||||
|
||||
ngx_int_t ngx_event_timer_init(ngx_log_t *log);
|
||||
ngx_msec_t ngx_event_find_timer(void);
|
||||
void ngx_event_expire_timers(void);
|
||||
ngx_int_t ngx_event_no_timers_left(void);
|
||||
|
||||
|
||||
extern ngx_rbtree_t ngx_event_timer_rbtree;
|
||||
|
||||
|
||||
static ngx_inline void
|
||||
ngx_event_del_timer(ngx_event_t *ev)
|
||||
{
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"event timer del: %d: %M",
|
||||
ngx_event_ident(ev->data), ev->timer.key);
|
||||
|
||||
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
ev->timer.left = NULL;
|
||||
ev->timer.right = NULL;
|
||||
ev->timer.parent = NULL;
|
||||
#endif
|
||||
|
||||
ev->timer_set = 0;
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline void
|
||||
ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
|
||||
{
|
||||
ngx_msec_t key;
|
||||
ngx_msec_int_t diff;
|
||||
|
||||
key = ngx_current_msec + timer;
|
||||
|
||||
if (ev->timer_set) {
|
||||
|
||||
/*
|
||||
* Use a previous timer value if difference between it and a new
|
||||
* value is less than NGX_TIMER_LAZY_DELAY milliseconds: this allows
|
||||
* to minimize the rbtree operations for fast connections.
|
||||
*/
|
||||
|
||||
diff = (ngx_msec_int_t) (key - ev->timer.key);
|
||||
|
||||
if (ngx_abs(diff) < NGX_TIMER_LAZY_DELAY) {
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"event timer: %d, old: %M, new: %M",
|
||||
ngx_event_ident(ev->data), ev->timer.key, key);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_del_timer(ev);
|
||||
}
|
||||
|
||||
ev->timer.key = key;
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"event timer add: %d: %M:%M",
|
||||
ngx_event_ident(ev->data), timer, ev->timer.key);
|
||||
|
||||
ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);
|
||||
|
||||
ev->timer_set = 1;
|
||||
}
|
||||
|
||||
|
||||
#endif /* _NGX_EVENT_TIMER_H_INCLUDED_ */
|
||||
@@ -0,0 +1,595 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
|
||||
|
||||
#if !(NGX_WIN32)
|
||||
|
||||
struct ngx_udp_connection_s {
|
||||
ngx_rbtree_node_t node;
|
||||
ngx_connection_t *connection;
|
||||
ngx_buf_t *buffer;
|
||||
};
|
||||
|
||||
|
||||
static void ngx_close_accepted_udp_connection(ngx_connection_t *c);
|
||||
static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf,
|
||||
size_t size);
|
||||
static ngx_int_t ngx_insert_udp_connection(ngx_connection_t *c);
|
||||
static ngx_connection_t *ngx_lookup_udp_connection(ngx_listening_t *ls,
|
||||
struct sockaddr *sockaddr, socklen_t socklen,
|
||||
struct sockaddr *local_sockaddr, socklen_t local_socklen);
|
||||
|
||||
|
||||
void
|
||||
ngx_event_recvmsg(ngx_event_t *ev)
|
||||
{
|
||||
ssize_t n;
|
||||
ngx_buf_t buf;
|
||||
ngx_log_t *log;
|
||||
ngx_err_t err;
|
||||
socklen_t socklen, local_socklen;
|
||||
ngx_event_t *rev, *wev;
|
||||
struct iovec iov[1];
|
||||
struct msghdr msg;
|
||||
ngx_sockaddr_t sa, lsa;
|
||||
struct sockaddr *sockaddr, *local_sockaddr;
|
||||
ngx_listening_t *ls;
|
||||
ngx_event_conf_t *ecf;
|
||||
ngx_connection_t *c, *lc;
|
||||
static u_char buffer[65535];
|
||||
|
||||
#if (NGX_HAVE_ADDRINFO_CMSG)
|
||||
u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
|
||||
#endif
|
||||
|
||||
if (ev->timedout) {
|
||||
if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev->timedout = 0;
|
||||
}
|
||||
|
||||
ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
|
||||
|
||||
if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
|
||||
ev->available = ecf->multi_accept;
|
||||
}
|
||||
|
||||
lc = ev->data;
|
||||
ls = lc->listening;
|
||||
ev->ready = 0;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
|
||||
"recvmsg on %V, ready: %d", &ls->addr_text, ev->available);
|
||||
|
||||
do {
|
||||
ngx_memzero(&msg, sizeof(struct msghdr));
|
||||
|
||||
iov[0].iov_base = (void *) buffer;
|
||||
iov[0].iov_len = sizeof(buffer);
|
||||
|
||||
msg.msg_name = &sa;
|
||||
msg.msg_namelen = sizeof(ngx_sockaddr_t);
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
#if (NGX_HAVE_ADDRINFO_CMSG)
|
||||
if (ls->wildcard) {
|
||||
msg.msg_control = &msg_control;
|
||||
msg.msg_controllen = sizeof(msg_control);
|
||||
|
||||
ngx_memzero(&msg_control, sizeof(msg_control));
|
||||
}
|
||||
#endif
|
||||
|
||||
n = recvmsg(lc->fd, &msg, 0);
|
||||
|
||||
if (n == -1) {
|
||||
err = ngx_socket_errno;
|
||||
|
||||
if (err == NGX_EAGAIN) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
|
||||
"recvmsg() not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, err, "recvmsg() failed");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_ADDRINFO_CMSG)
|
||||
if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
|
||||
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
|
||||
"recvmsg() truncated data");
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
sockaddr = msg.msg_name;
|
||||
socklen = msg.msg_namelen;
|
||||
|
||||
if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
|
||||
socklen = sizeof(ngx_sockaddr_t);
|
||||
}
|
||||
|
||||
if (socklen == 0) {
|
||||
|
||||
/*
|
||||
* on Linux recvmsg() returns zero msg_namelen
|
||||
* when receiving packets from unbound AF_UNIX sockets
|
||||
*/
|
||||
|
||||
socklen = sizeof(struct sockaddr);
|
||||
ngx_memzero(&sa, sizeof(struct sockaddr));
|
||||
sa.sockaddr.sa_family = ls->sockaddr->sa_family;
|
||||
}
|
||||
|
||||
local_sockaddr = ls->sockaddr;
|
||||
local_socklen = ls->socklen;
|
||||
|
||||
#if (NGX_HAVE_ADDRINFO_CMSG)
|
||||
|
||||
if (ls->wildcard) {
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
ngx_memcpy(&lsa, local_sockaddr, local_socklen);
|
||||
local_sockaddr = &lsa.sockaddr;
|
||||
|
||||
for (cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg != NULL;
|
||||
cmsg = CMSG_NXTHDR(&msg, cmsg))
|
||||
{
|
||||
if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
c = ngx_lookup_udp_connection(ls, sockaddr, socklen, local_sockaddr,
|
||||
local_socklen);
|
||||
|
||||
if (c) {
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
|
||||
ngx_log_handler_pt handler;
|
||||
|
||||
handler = c->log->handler;
|
||||
c->log->handler = NULL;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"recvmsg: fd:%d n:%z", c->fd, n);
|
||||
|
||||
c->log->handler = handler;
|
||||
}
|
||||
#endif
|
||||
|
||||
ngx_memzero(&buf, sizeof(ngx_buf_t));
|
||||
|
||||
buf.pos = buffer;
|
||||
buf.last = buffer + n;
|
||||
|
||||
rev = c->read;
|
||||
|
||||
c->udp->buffer = &buf;
|
||||
|
||||
rev->ready = 1;
|
||||
rev->active = 0;
|
||||
|
||||
rev->handler(rev);
|
||||
|
||||
if (c->udp) {
|
||||
c->udp->buffer = NULL;
|
||||
}
|
||||
|
||||
rev->ready = 0;
|
||||
rev->active = 1;
|
||||
|
||||
goto next;
|
||||
}
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
(void) ngx_atomic_fetch_add(ngx_stat_accepted, 1);
|
||||
#endif
|
||||
|
||||
ngx_accept_disabled = ngx_cycle->connection_n / 8
|
||||
- ngx_cycle->free_connection_n;
|
||||
|
||||
c = ngx_get_connection(lc->fd, ev->log);
|
||||
if (c == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
c->shared = 1;
|
||||
c->type = SOCK_DGRAM;
|
||||
c->socklen = socklen;
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
(void) ngx_atomic_fetch_add(ngx_stat_active, 1);
|
||||
#endif
|
||||
|
||||
c->pool = ngx_create_pool(ls->pool_size, ev->log);
|
||||
if (c->pool == NULL) {
|
||||
ngx_close_accepted_udp_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
c->sockaddr = ngx_palloc(c->pool, socklen);
|
||||
if (c->sockaddr == NULL) {
|
||||
ngx_close_accepted_udp_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_memcpy(c->sockaddr, sockaddr, socklen);
|
||||
|
||||
log = ngx_palloc(c->pool, sizeof(ngx_log_t));
|
||||
if (log == NULL) {
|
||||
ngx_close_accepted_udp_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
*log = ls->log;
|
||||
|
||||
c->recv = ngx_udp_shared_recv;
|
||||
c->send = ngx_udp_send;
|
||||
c->send_chain = ngx_udp_send_chain;
|
||||
|
||||
c->need_flush_buf = 1;
|
||||
|
||||
c->log = log;
|
||||
c->pool->log = log;
|
||||
c->listening = ls;
|
||||
|
||||
if (local_sockaddr == &lsa.sockaddr) {
|
||||
local_sockaddr = ngx_palloc(c->pool, local_socklen);
|
||||
if (local_sockaddr == NULL) {
|
||||
ngx_close_accepted_udp_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_memcpy(local_sockaddr, &lsa, local_socklen);
|
||||
}
|
||||
|
||||
c->local_sockaddr = local_sockaddr;
|
||||
c->local_socklen = local_socklen;
|
||||
|
||||
c->buffer = ngx_create_temp_buf(c->pool, n);
|
||||
if (c->buffer == NULL) {
|
||||
ngx_close_accepted_udp_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
c->buffer->last = ngx_cpymem(c->buffer->last, buffer, n);
|
||||
|
||||
rev = c->read;
|
||||
wev = c->write;
|
||||
|
||||
rev->active = 1;
|
||||
wev->ready = 1;
|
||||
|
||||
rev->log = log;
|
||||
wev->log = log;
|
||||
|
||||
/*
|
||||
* TODO: MT: - ngx_atomic_fetch_add()
|
||||
* or protection by critical section or light mutex
|
||||
*
|
||||
* TODO: MP: - allocated in a shared memory
|
||||
* - ngx_atomic_fetch_add()
|
||||
* or protection by critical section or light mutex
|
||||
*/
|
||||
|
||||
c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
|
||||
|
||||
c->start_time = ngx_current_msec;
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
(void) ngx_atomic_fetch_add(ngx_stat_handled, 1);
|
||||
#endif
|
||||
|
||||
if (ls->addr_ntop) {
|
||||
c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
|
||||
if (c->addr_text.data == NULL) {
|
||||
ngx_close_accepted_udp_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
|
||||
c->addr_text.data,
|
||||
ls->addr_text_max_len, 0);
|
||||
if (c->addr_text.len == 0) {
|
||||
ngx_close_accepted_udp_connection(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
{
|
||||
ngx_str_t addr;
|
||||
u_char text[NGX_SOCKADDR_STRLEN];
|
||||
|
||||
ngx_debug_accepted_connection(ecf, c);
|
||||
|
||||
if (log->log_level & NGX_LOG_DEBUG_EVENT) {
|
||||
addr.data = text;
|
||||
addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text,
|
||||
NGX_SOCKADDR_STRLEN, 1);
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0,
|
||||
"*%uA recvmsg: %V fd:%d n:%z",
|
||||
c->number, &addr, c->fd, n);
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ngx_insert_udp_connection(c) != NGX_OK) {
|
||||
ngx_close_accepted_udp_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
log->data = NULL;
|
||||
log->handler = NULL;
|
||||
|
||||
ls->handler(c);
|
||||
|
||||
next:
|
||||
|
||||
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
|
||||
ev->available -= n;
|
||||
}
|
||||
|
||||
} while (ev->available);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_close_accepted_udp_connection(ngx_connection_t *c)
|
||||
{
|
||||
ngx_free_connection(c);
|
||||
|
||||
c->fd = (ngx_socket_t) -1;
|
||||
|
||||
if (c->pool) {
|
||||
ngx_destroy_pool(c->pool);
|
||||
}
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static ssize_t
|
||||
ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf, size_t size)
|
||||
{
|
||||
ssize_t n;
|
||||
ngx_buf_t *b;
|
||||
|
||||
if (c->udp == NULL || c->udp->buffer == NULL) {
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
b = c->udp->buffer;
|
||||
|
||||
n = ngx_min(b->last - b->pos, (ssize_t) size);
|
||||
|
||||
ngx_memcpy(buf, b->pos, n);
|
||||
|
||||
c->udp->buffer = NULL;
|
||||
|
||||
c->read->ready = 0;
|
||||
c->read->active = 1;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp,
|
||||
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_connection_t *c, *ct;
|
||||
ngx_rbtree_node_t **p;
|
||||
ngx_udp_connection_t *udp, *udpt;
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
if (node->key < temp->key) {
|
||||
|
||||
p = &temp->left;
|
||||
|
||||
} else if (node->key > temp->key) {
|
||||
|
||||
p = &temp->right;
|
||||
|
||||
} else { /* node->key == temp->key */
|
||||
|
||||
udp = (ngx_udp_connection_t *) node;
|
||||
c = udp->connection;
|
||||
|
||||
udpt = (ngx_udp_connection_t *) temp;
|
||||
ct = udpt->connection;
|
||||
|
||||
rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen,
|
||||
ct->sockaddr, ct->socklen, 1);
|
||||
|
||||
if (rc == 0 && c->listening->wildcard) {
|
||||
rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen,
|
||||
ct->local_sockaddr, ct->local_socklen, 1);
|
||||
}
|
||||
|
||||
p = (rc < 0) ? &temp->left : &temp->right;
|
||||
}
|
||||
|
||||
if (*p == sentinel) {
|
||||
break;
|
||||
}
|
||||
|
||||
temp = *p;
|
||||
}
|
||||
|
||||
*p = node;
|
||||
node->parent = temp;
|
||||
node->left = sentinel;
|
||||
node->right = sentinel;
|
||||
ngx_rbt_red(node);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_insert_udp_connection(ngx_connection_t *c)
|
||||
{
|
||||
uint32_t hash;
|
||||
ngx_pool_cleanup_t *cln;
|
||||
ngx_udp_connection_t *udp;
|
||||
|
||||
if (c->udp) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
udp = ngx_pcalloc(c->pool, sizeof(ngx_udp_connection_t));
|
||||
if (udp == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
udp->connection = c;
|
||||
|
||||
ngx_crc32_init(hash);
|
||||
ngx_crc32_update(&hash, (u_char *) c->sockaddr, c->socklen);
|
||||
|
||||
if (c->listening->wildcard) {
|
||||
ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen);
|
||||
}
|
||||
|
||||
ngx_crc32_final(hash);
|
||||
|
||||
udp->node.key = hash;
|
||||
|
||||
cln = ngx_pool_cleanup_add(c->pool, 0);
|
||||
if (cln == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cln->data = c;
|
||||
cln->handler = ngx_delete_udp_connection;
|
||||
|
||||
ngx_rbtree_insert(&c->listening->rbtree, &udp->node);
|
||||
|
||||
c->udp = udp;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_delete_udp_connection(void *data)
|
||||
{
|
||||
ngx_connection_t *c = data;
|
||||
|
||||
if (c->udp == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_rbtree_delete(&c->listening->rbtree, &c->udp->node);
|
||||
|
||||
c->udp = NULL;
|
||||
}
|
||||
|
||||
|
||||
static ngx_connection_t *
|
||||
ngx_lookup_udp_connection(ngx_listening_t *ls, struct sockaddr *sockaddr,
|
||||
socklen_t socklen, struct sockaddr *local_sockaddr, socklen_t local_socklen)
|
||||
{
|
||||
uint32_t hash;
|
||||
ngx_int_t rc;
|
||||
ngx_connection_t *c;
|
||||
ngx_rbtree_node_t *node, *sentinel;
|
||||
ngx_udp_connection_t *udp;
|
||||
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
|
||||
if (sockaddr->sa_family == AF_UNIX) {
|
||||
struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr;
|
||||
|
||||
if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path)
|
||||
|| saun->sun_path[0] == '\0')
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
|
||||
"unbound unix socket");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
node = ls->rbtree.root;
|
||||
sentinel = ls->rbtree.sentinel;
|
||||
|
||||
ngx_crc32_init(hash);
|
||||
ngx_crc32_update(&hash, (u_char *) sockaddr, socklen);
|
||||
|
||||
if (ls->wildcard) {
|
||||
ngx_crc32_update(&hash, (u_char *) local_sockaddr, local_socklen);
|
||||
}
|
||||
|
||||
ngx_crc32_final(hash);
|
||||
|
||||
while (node != sentinel) {
|
||||
|
||||
if (hash < node->key) {
|
||||
node = node->left;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hash > node->key) {
|
||||
node = node->right;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* hash == node->key */
|
||||
|
||||
udp = (ngx_udp_connection_t *) node;
|
||||
|
||||
c = udp->connection;
|
||||
|
||||
rc = ngx_cmp_sockaddr(sockaddr, socklen,
|
||||
c->sockaddr, c->socklen, 1);
|
||||
|
||||
if (rc == 0 && ls->wildcard) {
|
||||
rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen,
|
||||
c->local_sockaddr, c->local_socklen, 1);
|
||||
}
|
||||
|
||||
if (rc == 0) {
|
||||
return c;
|
||||
}
|
||||
|
||||
node = (rc < 0) ? node->left : node->right;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void
|
||||
ngx_delete_udp_connection(void *data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Nginx, Inc.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_EVENT_UDP_H_INCLUDED_
|
||||
#define _NGX_EVENT_UDP_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
#if !(NGX_WIN32)
|
||||
|
||||
#if ((NGX_HAVE_MSGHDR_MSG_CONTROL) \
|
||||
&& (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_RECVDSTADDR \
|
||||
|| NGX_HAVE_IP_PKTINFO \
|
||||
|| (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)))
|
||||
#define NGX_HAVE_ADDRINFO_CMSG 1
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if (NGX_HAVE_ADDRINFO_CMSG)
|
||||
|
||||
typedef union {
|
||||
#if (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_RECVDSTADDR)
|
||||
struct in_addr addr;
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_IP_PKTINFO)
|
||||
struct in_pktinfo pkt;
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
|
||||
struct in6_pktinfo pkt6;
|
||||
#endif
|
||||
} ngx_addrinfo_t;
|
||||
|
||||
size_t ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg,
|
||||
struct sockaddr *local_sockaddr);
|
||||
ngx_int_t ngx_get_srcaddr_cmsg(struct cmsghdr *cmsg,
|
||||
struct sockaddr *local_sockaddr);
|
||||
|
||||
#endif
|
||||
|
||||
void ngx_event_recvmsg(ngx_event_t *ev);
|
||||
ssize_t ngx_sendmsg(ngx_connection_t *c, struct msghdr *msg, int flags);
|
||||
void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp,
|
||||
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
|
||||
#endif
|
||||
|
||||
void ngx_delete_udp_connection(void *data);
|
||||
|
||||
|
||||
#endif /* _NGX_EVENT_UDP_H_INCLUDED_ */
|
||||
Reference in New Issue
Block a user