|
|
@ -1,3 +1,29 @@ |
|
|
|
/* Copyright (c) 2008,2009 Ryan Dahl
|
|
|
|
* |
|
|
|
* oi_queue comes from ngx_queue.h |
|
|
|
* Copyright (C) 2002-2009 Igor Sysoev |
|
|
|
* |
|
|
|
* Redistribution and use in source and binary forms, with or without |
|
|
|
* modification, are permitted provided that the following conditions |
|
|
|
* are met: |
|
|
|
* 1. Redistributions of source code must retain the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer. |
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer in the |
|
|
|
* documentation and/or other materials provided with the distribution. |
|
|
|
* |
|
|
|
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE |
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
|
|
* SUCH DAMAGE. |
|
|
|
*/ |
|
|
|
#include <stdio.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <assert.h> |
|
|
@ -21,9 +47,13 @@ |
|
|
|
|
|
|
|
#if HAVE_GNUTLS |
|
|
|
# include <gnutls/gnutls.h> |
|
|
|
# define GNUTLS_NEED_WRITE (gnutls_record_get_direction(socket->session) == 1) |
|
|
|
# define GNUTLS_NEED_READ (gnutls_record_get_direction(socket->session) == 0) |
|
|
|
#endif |
|
|
|
#endif // HAVE_GNUTLS
|
|
|
|
|
|
|
|
/* a few forwards
|
|
|
|
* they wont even be defined if not having gnutls |
|
|
|
* */ |
|
|
|
static int secure_full_goodbye (oi_socket *socket); |
|
|
|
static int secure_half_goodbye (oi_socket *socket); |
|
|
|
|
|
|
|
#undef TRUE |
|
|
|
#define TRUE 1 |
|
|
@ -36,30 +66,56 @@ |
|
|
|
#define AGAIN 1 |
|
|
|
#define ERROR 2 |
|
|
|
|
|
|
|
#define RAISE_ERROR(s, _domain, _code) do { \ |
|
|
|
if(s->on_error) { \ |
|
|
|
struct oi_error __oi_error; \ |
|
|
|
__oi_error.domain = _domain; \ |
|
|
|
__oi_error.code = _code; \ |
|
|
|
s->on_error(s, __oi_error); \ |
|
|
|
} \ |
|
|
|
} while(0) \ |
|
|
|
void |
|
|
|
oi_buf_destroy (oi_buf *buf) |
|
|
|
{ |
|
|
|
free(buf->base); |
|
|
|
free(buf); |
|
|
|
} |
|
|
|
|
|
|
|
oi_buf * |
|
|
|
oi_buf_new2 (size_t len) |
|
|
|
{ |
|
|
|
oi_buf *buf = malloc(sizeof(oi_buf)); |
|
|
|
if(!buf) |
|
|
|
return NULL; |
|
|
|
buf->base = malloc(len); |
|
|
|
if(!buf->base) { |
|
|
|
free(buf); |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
buf->len = len; |
|
|
|
buf->release = oi_buf_destroy; |
|
|
|
return buf; |
|
|
|
} |
|
|
|
|
|
|
|
oi_buf * |
|
|
|
oi_buf_new (const char *base, size_t len) |
|
|
|
{ |
|
|
|
oi_buf *buf = oi_buf_new2(len); |
|
|
|
if(!buf) |
|
|
|
return NULL; |
|
|
|
memcpy(buf->base, base, len); |
|
|
|
return buf; |
|
|
|
} |
|
|
|
|
|
|
|
#define CLOSE_ASAP(socket) do { \ |
|
|
|
if ((socket)->read_action) { \ |
|
|
|
(socket)->read_action = full_close; \ |
|
|
|
} \ |
|
|
|
if ((socket)->write_action) { \ |
|
|
|
(socket)->write_action = full_close; \ |
|
|
|
} \ |
|
|
|
} while (0) |
|
|
|
|
|
|
|
static int |
|
|
|
full_close(oi_socket *socket) |
|
|
|
{ |
|
|
|
if(-1 == close(socket->fd) && errno == EINTR) { |
|
|
|
/* TODO fd still open. next loop call close again? */ |
|
|
|
assert(0 && "implement me"); |
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
if (close(socket->fd) == -1) |
|
|
|
return errno == EINTR ? AGAIN : ERROR; |
|
|
|
|
|
|
|
socket->read_action = NULL; |
|
|
|
socket->write_action = NULL; |
|
|
|
|
|
|
|
if(socket->attached) { |
|
|
|
ev_feed_event(SOCKET_LOOP_ &socket->read_watcher, EV_READ); |
|
|
|
} |
|
|
|
return OKAY; |
|
|
|
} |
|
|
|
|
|
|
@ -67,43 +123,79 @@ static int |
|
|
|
half_close(oi_socket *socket) |
|
|
|
{ |
|
|
|
int r = shutdown(socket->fd, SHUT_WR); |
|
|
|
|
|
|
|
if(r == -1) { |
|
|
|
RAISE_ERROR(socket, OI_ERROR_SHUTDOWN, errno); |
|
|
|
if (r == -1) { |
|
|
|
socket->errorno = errno; |
|
|
|
assert(0 && "Shouldn't get an error on shutdown"); |
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
|
|
|
|
socket->write_action = NULL; |
|
|
|
|
|
|
|
/* TODO set timer to zero so we get a callback soon */ |
|
|
|
return OKAY; |
|
|
|
} |
|
|
|
|
|
|
|
// This is to be called when ever the out_stream is empty
|
|
|
|
// and we need to change state.
|
|
|
|
static void |
|
|
|
update_write_buffer_after_send(oi_socket *socket, ssize_t sent) |
|
|
|
change_state_for_empty_out_stream (oi_socket *socket) |
|
|
|
{ |
|
|
|
/*
|
|
|
|
* a very complicated bunch of close logic! |
|
|
|
* XXX this is awful. FIXME |
|
|
|
*/ |
|
|
|
if (socket->got_half_close == FALSE) { |
|
|
|
if (socket->got_full_close == FALSE) { |
|
|
|
/* Normal situation. Didn't get any close signals. */ |
|
|
|
ev_io_stop(SOCKET_LOOP_ &socket->write_watcher); |
|
|
|
} else { |
|
|
|
/* Got Full Close. */ |
|
|
|
if (socket->read_action) |
|
|
|
#if HAVE_GNUTLS |
|
|
|
socket->read_action = socket->secure ? secure_full_goodbye : full_close; |
|
|
|
#else |
|
|
|
socket->read_action = full_close; |
|
|
|
#endif |
|
|
|
|
|
|
|
if (socket->write_action) |
|
|
|
#if HAVE_GNUTLS |
|
|
|
socket->write_action = socket->secure ? secure_full_goodbye : full_close; |
|
|
|
#else |
|
|
|
socket->write_action = full_close; |
|
|
|
#endif |
|
|
|
} |
|
|
|
} else { |
|
|
|
/* Got Half Close. */ |
|
|
|
if (socket->write_action) |
|
|
|
#if HAVE_GNUTLS |
|
|
|
socket->write_action = socket->secure ? secure_half_goodbye : half_close; |
|
|
|
#else |
|
|
|
socket->write_action = half_close; |
|
|
|
#endif |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void |
|
|
|
update_write_buffer_after_send (oi_socket *socket, ssize_t sent) |
|
|
|
{ |
|
|
|
oi_queue *q = oi_queue_last(&socket->out_stream); |
|
|
|
oi_buf *to_write = oi_queue_data(q, oi_buf, queue); |
|
|
|
to_write->written += sent; |
|
|
|
socket->written += sent; |
|
|
|
|
|
|
|
if(to_write->written == to_write->len) { |
|
|
|
if (to_write->written == to_write->len) { |
|
|
|
|
|
|
|
oi_queue_remove(q); |
|
|
|
|
|
|
|
if(to_write->release) { |
|
|
|
if (to_write->release) { |
|
|
|
to_write->release(to_write); |
|
|
|
} |
|
|
|
|
|
|
|
if(oi_queue_empty(&socket->out_stream)) { |
|
|
|
ev_io_stop(SOCKET_LOOP_ &socket->write_watcher); |
|
|
|
if(socket->on_drain) |
|
|
|
if (oi_queue_empty(&socket->out_stream)) { |
|
|
|
change_state_for_empty_out_stream(socket); |
|
|
|
if (socket->on_drain) |
|
|
|
socket->on_drain(socket); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#if HAVE_GNUTLS |
|
|
|
static int secure_socket_send(oi_socket *socket); |
|
|
|
static int secure_socket_recv(oi_socket *socket); |
|
|
@ -123,7 +215,7 @@ nosigpipe_push(gnutls_transport_ptr_t data, const void *buf, size_t len) |
|
|
|
#endif |
|
|
|
int r = send(socket->fd, buf, len, flags); |
|
|
|
|
|
|
|
if(r == -1) { |
|
|
|
if (r == -1) { |
|
|
|
gnutls_transport_set_errno(socket->session, errno); /* necessary ? */ |
|
|
|
} |
|
|
|
|
|
|
@ -137,26 +229,25 @@ secure_handshake(oi_socket *socket) |
|
|
|
|
|
|
|
int r = gnutls_handshake(socket->session); |
|
|
|
|
|
|
|
if(gnutls_error_is_fatal(r)) { |
|
|
|
RAISE_ERROR(socket, OI_ERROR_GNUTLS, r); |
|
|
|
if (gnutls_error_is_fatal(r)) { |
|
|
|
socket->gnutls_errorno = r; |
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
|
|
|
|
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|
|
|
if (r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|
|
|
return AGAIN; |
|
|
|
|
|
|
|
oi_socket_reset_timeout(socket); |
|
|
|
|
|
|
|
if(!socket->connected) { |
|
|
|
if (!socket->connected) { |
|
|
|
socket->connected = TRUE; |
|
|
|
if(socket->on_connect) |
|
|
|
socket->on_connect(socket); |
|
|
|
if (socket->on_connect) socket->on_connect(socket); |
|
|
|
} |
|
|
|
|
|
|
|
if(socket->read_action) |
|
|
|
if (socket->read_action) |
|
|
|
socket->read_action = secure_socket_recv; |
|
|
|
|
|
|
|
if(socket->write_action) |
|
|
|
if (socket->write_action) |
|
|
|
socket->write_action = secure_socket_send; |
|
|
|
|
|
|
|
return OKAY; |
|
|
@ -167,7 +258,7 @@ secure_socket_send(oi_socket *socket) |
|
|
|
{ |
|
|
|
ssize_t sent; |
|
|
|
|
|
|
|
if(oi_queue_empty(&socket->out_stream)) { |
|
|
|
if (oi_queue_empty(&socket->out_stream)) { |
|
|
|
ev_io_stop(SOCKET_LOOP_ &socket->write_watcher); |
|
|
|
return AGAIN; |
|
|
|
} |
|
|
@ -182,32 +273,23 @@ secure_socket_send(oi_socket *socket) |
|
|
|
, to_write->len - to_write->written |
|
|
|
); |
|
|
|
|
|
|
|
if(gnutls_error_is_fatal(sent)) { |
|
|
|
RAISE_ERROR(socket, OI_ERROR_GNUTLS, sent); |
|
|
|
if (gnutls_error_is_fatal(sent)) { |
|
|
|
socket->gnutls_errorno = sent; |
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
|
|
|
|
if(sent == 0) |
|
|
|
if (sent == 0) |
|
|
|
return AGAIN; |
|
|
|
|
|
|
|
oi_socket_reset_timeout(socket); |
|
|
|
|
|
|
|
if(sent == GNUTLS_E_INTERRUPTED || sent == GNUTLS_E_AGAIN) { |
|
|
|
if(GNUTLS_NEED_READ) { |
|
|
|
if(socket->read_action) { |
|
|
|
socket->read_action = secure_socket_send; |
|
|
|
} else { |
|
|
|
/* TODO GnuTLS needs read but already got EOF */ |
|
|
|
assert(0 && "needs read but already got EOF"); |
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
} |
|
|
|
if (sent == GNUTLS_E_INTERRUPTED || sent == GNUTLS_E_AGAIN) { |
|
|
|
return AGAIN; |
|
|
|
} |
|
|
|
|
|
|
|
if(sent > 0) { |
|
|
|
if (sent > 0) { |
|
|
|
/* make sure the callbacks are correct */ |
|
|
|
if(socket->read_action) |
|
|
|
if (socket->read_action) |
|
|
|
socket->read_action = secure_socket_recv; |
|
|
|
update_write_buffer_after_send(socket, sent); |
|
|
|
return OKAY; |
|
|
@ -220,8 +302,8 @@ secure_socket_send(oi_socket *socket) |
|
|
|
static int |
|
|
|
secure_socket_recv(oi_socket *socket) |
|
|
|
{ |
|
|
|
char recv_buffer[TCP_MAXWIN]; |
|
|
|
size_t recv_buffer_size = MIN(TCP_MAXWIN, socket->chunksize); |
|
|
|
char recv_buffer[socket->chunksize]; |
|
|
|
size_t recv_buffer_size = socket->chunksize; |
|
|
|
ssize_t recved; |
|
|
|
|
|
|
|
assert(socket->secure); |
|
|
@ -230,22 +312,12 @@ secure_socket_recv(oi_socket *socket) |
|
|
|
|
|
|
|
//printf("secure socket recv %d %p\n", recved, socket->on_connect);
|
|
|
|
|
|
|
|
if(gnutls_error_is_fatal(recved)) { |
|
|
|
RAISE_ERROR(socket, OI_ERROR_GNUTLS, recved); |
|
|
|
if (gnutls_error_is_fatal(recved)) { |
|
|
|
socket->gnutls_errorno = recved; |
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
|
|
|
|
if(recved == GNUTLS_E_INTERRUPTED || recved == GNUTLS_E_AGAIN) { |
|
|
|
if(GNUTLS_NEED_WRITE) { |
|
|
|
if(socket->write_action) { |
|
|
|
printf("need write\n"); |
|
|
|
socket->write_action = secure_socket_recv; |
|
|
|
} else { |
|
|
|
/* TODO GnuTLS needs send but already closed write end */ |
|
|
|
assert(0 && "needs read but cannot"); |
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
} |
|
|
|
if (recved == GNUTLS_E_INTERRUPTED || recved == GNUTLS_E_AGAIN) { |
|
|
|
return AGAIN; |
|
|
|
} |
|
|
|
|
|
|
@ -254,27 +326,27 @@ secure_socket_recv(oi_socket *socket) |
|
|
|
/* A server may also receive GNUTLS_E_REHANDSHAKE when a client has
|
|
|
|
* initiated a handshake. In that case the server can only initiate a |
|
|
|
* handshake or terminate the connection. */ |
|
|
|
if(recved == GNUTLS_E_REHANDSHAKE) { |
|
|
|
if(socket->write_action) { |
|
|
|
if (recved == GNUTLS_E_REHANDSHAKE) { |
|
|
|
if (socket->write_action) { |
|
|
|
socket->read_action = secure_handshake; |
|
|
|
socket->write_action = secure_handshake; |
|
|
|
return OKAY; |
|
|
|
} else { |
|
|
|
/* TODO */ |
|
|
|
assert(0 && "needs read but cannot"); |
|
|
|
socket->read_action = full_close; |
|
|
|
// set error
|
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if(recved >= 0) { |
|
|
|
if (recved >= 0) { |
|
|
|
/* Got EOF */ |
|
|
|
if(recved == 0) |
|
|
|
if (recved == 0) |
|
|
|
socket->read_action = NULL; |
|
|
|
|
|
|
|
if(socket->write_action) |
|
|
|
if (socket->write_action) |
|
|
|
socket->write_action = secure_socket_send; |
|
|
|
|
|
|
|
if(socket->on_read) { socket->on_read(socket, recv_buffer, recved); } |
|
|
|
if (socket->on_read) { socket->on_read(socket, recv_buffer, recved); } |
|
|
|
|
|
|
|
return OKAY; |
|
|
|
} |
|
|
@ -284,51 +356,46 @@ secure_socket_recv(oi_socket *socket) |
|
|
|
} |
|
|
|
|
|
|
|
static int |
|
|
|
secure_goodbye(oi_socket *socket, gnutls_close_request_t how) |
|
|
|
secure_full_goodbye (oi_socket *socket) |
|
|
|
{ |
|
|
|
assert(socket->secure); |
|
|
|
|
|
|
|
int r = gnutls_bye(socket->session, how); |
|
|
|
int r = gnutls_bye(socket->session, GNUTLS_SHUT_RDWR); |
|
|
|
|
|
|
|
if(gnutls_error_is_fatal(r)) { |
|
|
|
RAISE_ERROR(socket, OI_ERROR_GNUTLS, r); |
|
|
|
if (gnutls_error_is_fatal(r)) { |
|
|
|
socket->gnutls_errorno = r; |
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
|
|
|
|
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|
|
|
if (r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|
|
|
return AGAIN; |
|
|
|
|
|
|
|
CLOSE_ASAP(socket); |
|
|
|
|
|
|
|
return OKAY; |
|
|
|
} |
|
|
|
|
|
|
|
static int |
|
|
|
secure_full_goodbye(oi_socket *socket) |
|
|
|
secure_half_goodbye (oi_socket *socket) |
|
|
|
{ |
|
|
|
int r = secure_goodbye(socket, GNUTLS_SHUT_RDWR); |
|
|
|
if(OKAY == r) { |
|
|
|
return full_close(socket); |
|
|
|
} |
|
|
|
return r; |
|
|
|
} |
|
|
|
assert(socket->secure); |
|
|
|
|
|
|
|
static int |
|
|
|
secure_half_goodbye(oi_socket *socket) |
|
|
|
{ |
|
|
|
int r = secure_goodbye(socket, GNUTLS_SHUT_WR); |
|
|
|
if(OKAY == r) { |
|
|
|
return half_close(socket); |
|
|
|
int r = gnutls_bye(socket->session, GNUTLS_SHUT_WR); |
|
|
|
|
|
|
|
if (gnutls_error_is_fatal(r)) { |
|
|
|
socket->gnutls_errorno = r; |
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
return r; |
|
|
|
|
|
|
|
if (r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|
|
|
return AGAIN; |
|
|
|
|
|
|
|
if (socket->write_action) |
|
|
|
socket->write_action = half_close; |
|
|
|
|
|
|
|
return OKAY; |
|
|
|
} |
|
|
|
|
|
|
|
/* Tells the socket to use transport layer security (SSL). liboi does not
|
|
|
|
* want to make any decisions about security requirements, so the |
|
|
|
* majoirty of GnuTLS configuration is left to the user. Only the transport |
|
|
|
* layer of GnuTLS is controlled by liboi. |
|
|
|
* |
|
|
|
* That is, do not use gnutls_transport_* functions. |
|
|
|
* Do use the rest of GnuTLS's API. |
|
|
|
*/ |
|
|
|
void |
|
|
|
oi_socket_set_secure_session (oi_socket *socket, gnutls_session_t session) |
|
|
|
{ |
|
|
@ -338,13 +405,13 @@ oi_socket_set_secure_session (oi_socket *socket, gnutls_session_t session) |
|
|
|
#endif /* HAVE GNUTLS */ |
|
|
|
|
|
|
|
static int |
|
|
|
socket_send(oi_socket *socket) |
|
|
|
socket_send (oi_socket *socket) |
|
|
|
{ |
|
|
|
ssize_t sent; |
|
|
|
|
|
|
|
assert(socket->secure == FALSE); |
|
|
|
|
|
|
|
if(oi_queue_empty(&socket->out_stream)) { |
|
|
|
if (oi_queue_empty(&socket->out_stream)) { |
|
|
|
ev_io_stop(SOCKET_LOOP_ &socket->write_watcher); |
|
|
|
return AGAIN; |
|
|
|
} |
|
|
@ -368,32 +435,36 @@ socket_send(oi_socket *socket) |
|
|
|
, flags |
|
|
|
); |
|
|
|
|
|
|
|
if(sent < 0) { |
|
|
|
switch(errno) { |
|
|
|
if (sent < 0) { |
|
|
|
switch (errno) { |
|
|
|
#ifdef EWOULDBLOCK |
|
|
|
case EWOULDBLOCK: |
|
|
|
#else |
|
|
|
case EAGAIN: |
|
|
|
#endif |
|
|
|
return AGAIN; |
|
|
|
|
|
|
|
case ECONNREFUSED: |
|
|
|
socket->errorno = errno; |
|
|
|
return ERROR; |
|
|
|
|
|
|
|
case ECONNRESET: |
|
|
|
socket->write_action = NULL; |
|
|
|
/* TODO maybe just clear write buffer instead of error?
|
|
|
|
* They should be able to read still from the socket. |
|
|
|
*/ |
|
|
|
RAISE_ERROR(socket, OI_ERROR_SEND, errno); |
|
|
|
socket->errorno = errno; |
|
|
|
return ERROR; |
|
|
|
|
|
|
|
default: |
|
|
|
perror("send()"); |
|
|
|
assert(0 && "oi shouldn't let this happen."); |
|
|
|
socket->errorno = errno; |
|
|
|
return ERROR; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
oi_socket_reset_timeout(socket); |
|
|
|
|
|
|
|
if(!socket->connected) { |
|
|
|
if (!socket->connected) { |
|
|
|
socket->connected = TRUE; |
|
|
|
if(socket->on_connect) { socket->on_connect(socket); } |
|
|
|
if (socket->on_connect) socket->on_connect(socket); |
|
|
|
} |
|
|
|
|
|
|
|
update_write_buffer_after_send(socket, sent); |
|
|
@ -402,7 +473,7 @@ socket_send(oi_socket *socket) |
|
|
|
} |
|
|
|
|
|
|
|
static int |
|
|
|
socket_recv(oi_socket *socket) |
|
|
|
socket_recv (oi_socket *socket) |
|
|
|
{ |
|
|
|
char buf[TCP_MAXWIN]; |
|
|
|
size_t buf_size = TCP_MAXWIN; |
|
|
@ -410,28 +481,30 @@ socket_recv(oi_socket *socket) |
|
|
|
|
|
|
|
assert(socket->secure == FALSE); |
|
|
|
|
|
|
|
if(!socket->connected) { |
|
|
|
if (!socket->connected) { |
|
|
|
socket->connected = TRUE; |
|
|
|
if(socket->on_connect) { socket->on_connect(socket); } |
|
|
|
if (socket->on_connect) socket->on_connect(socket); |
|
|
|
return OKAY; |
|
|
|
} |
|
|
|
|
|
|
|
recved = recv(socket->fd, buf, buf_size, 0); |
|
|
|
|
|
|
|
if(recved < 0) { |
|
|
|
switch(errno) { |
|
|
|
case EAGAIN: |
|
|
|
if (recved < 0) { |
|
|
|
switch (errno) { |
|
|
|
#ifdef EWOULDBLOCK |
|
|
|
case EWOULDBLOCK: |
|
|
|
#else |
|
|
|
case EAGAIN: |
|
|
|
#endif |
|
|
|
return AGAIN; |
|
|
|
|
|
|
|
case EINTR: |
|
|
|
return AGAIN; |
|
|
|
|
|
|
|
/* A remote host refused to allow the network connection (typically
|
|
|
|
* because it is not running the requested service). */ |
|
|
|
case ECONNREFUSED: |
|
|
|
RAISE_ERROR(socket, OI_ERROR_RECV, errno); |
|
|
|
return ERROR; |
|
|
|
|
|
|
|
case ECONNRESET: |
|
|
|
RAISE_ERROR(socket, OI_ERROR_RECV, errno); |
|
|
|
socket->errorno = errno; |
|
|
|
return ERROR; |
|
|
|
|
|
|
|
default: |
|
|
@ -444,19 +517,19 @@ socket_recv(oi_socket *socket) |
|
|
|
|
|
|
|
oi_socket_reset_timeout(socket); |
|
|
|
|
|
|
|
if(recved == 0) { |
|
|
|
if (recved == 0) { |
|
|
|
oi_socket_read_stop(socket); |
|
|
|
socket->read_action = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
/* NOTE: EOF is signaled with recved == 0 on callback */ |
|
|
|
if(socket->on_read) { socket->on_read(socket, buf, recved); } |
|
|
|
if (socket->on_read) { socket->on_read(socket, buf, recved); } |
|
|
|
|
|
|
|
return OKAY; |
|
|
|
} |
|
|
|
|
|
|
|
static void |
|
|
|
assign_file_descriptor(oi_socket *socket, int fd) |
|
|
|
assign_file_descriptor (oi_socket *socket, int fd) |
|
|
|
{ |
|
|
|
socket->fd = fd; |
|
|
|
|
|
|
@ -467,7 +540,7 @@ assign_file_descriptor(oi_socket *socket, int fd) |
|
|
|
socket->write_action = socket_send; |
|
|
|
|
|
|
|
#if HAVE_GNUTLS |
|
|
|
if(socket->secure) { |
|
|
|
if (socket->secure) { |
|
|
|
gnutls_transport_set_lowat(socket->session, 0); |
|
|
|
gnutls_transport_set_push_function(socket->session, nosigpipe_push); |
|
|
|
gnutls_transport_set_ptr2 ( socket->session |
|
|
@ -485,7 +558,7 @@ assign_file_descriptor(oi_socket *socket, int fd) |
|
|
|
* Called by server->connection_watcher. |
|
|
|
*/ |
|
|
|
static void |
|
|
|
on_connection(EV_P_ ev_io *watcher, int revents) |
|
|
|
on_connection (EV_P_ ev_io *watcher, int revents) |
|
|
|
{ |
|
|
|
oi_server *server = watcher->data; |
|
|
|
|
|
|
@ -497,7 +570,7 @@ on_connection(EV_P_ ev_io *watcher, int revents) |
|
|
|
#endif |
|
|
|
assert(&server->connection_watcher == watcher); |
|
|
|
|
|
|
|
if(EV_ERROR & revents) { |
|
|
|
if (EV_ERROR & revents) { |
|
|
|
oi_server_close(server); |
|
|
|
return; |
|
|
|
} |
|
|
@ -507,23 +580,23 @@ on_connection(EV_P_ ev_io *watcher, int revents) |
|
|
|
|
|
|
|
/* TODO accept all possible connections? currently: just one */ |
|
|
|
int fd = accept(server->fd, (struct sockaddr*)&address, &addr_len); |
|
|
|
if(fd < 0) { |
|
|
|
if (fd < 0) { |
|
|
|
perror("accept()"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
oi_socket *socket = NULL; |
|
|
|
if(server->on_connection) |
|
|
|
if (server->on_connection) |
|
|
|
socket = server->on_connection(server, (struct sockaddr*)&address, addr_len); |
|
|
|
|
|
|
|
if(socket == NULL) { |
|
|
|
if (socket == NULL) { |
|
|
|
close(fd); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
int flags = fcntl(fd, F_GETFL, 0); |
|
|
|
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
|
|
|
if(r < 0) { |
|
|
|
if (r < 0) { |
|
|
|
/* TODO error report */ |
|
|
|
} |
|
|
|
|
|
|
@ -541,21 +614,20 @@ int |
|
|
|
oi_server_listen(oi_server *server, struct addrinfo *addrinfo) |
|
|
|
{ |
|
|
|
int fd = -1; |
|
|
|
struct linger ling = {0, 0}; |
|
|
|
assert(server->listening == FALSE); |
|
|
|
|
|
|
|
fd = socket( addrinfo->ai_family |
|
|
|
, addrinfo->ai_socktype |
|
|
|
, addrinfo->ai_protocol |
|
|
|
); |
|
|
|
if(fd < 0) { |
|
|
|
if (fd < 0) { |
|
|
|
perror("socket()"); |
|
|
|
return -1; |
|
|
|
} |
|
|
|
|
|
|
|
int flags = fcntl(fd, F_GETFL, 0); |
|
|
|
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
|
|
|
if(r < 0) { |
|
|
|
if (r < 0) { |
|
|
|
perror("fcntl()"); |
|
|
|
return -1; |
|
|
|
} |
|
|
@ -563,7 +635,6 @@ oi_server_listen(oi_server *server, struct addrinfo *addrinfo) |
|
|
|
flags = 1; |
|
|
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags)); |
|
|
|
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags)); |
|
|
|
setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling)); |
|
|
|
|
|
|
|
/* XXX: Sending single byte chunks in a response body? Perhaps there is a
|
|
|
|
* need to enable the Nagel algorithm dynamically. For now disabling. |
|
|
@ -596,7 +667,7 @@ oi_server_listen(oi_server *server, struct addrinfo *addrinfo) |
|
|
|
void |
|
|
|
oi_server_close(oi_server *server) |
|
|
|
{ |
|
|
|
if(server->listening) { |
|
|
|
if (server->listening) { |
|
|
|
oi_server_detach(server); |
|
|
|
close(server->fd); |
|
|
|
/* TODO do this on the loop? check return value? */ |
|
|
@ -647,23 +718,20 @@ on_timeout(EV_P_ ev_timer *watcher, int revents) |
|
|
|
|
|
|
|
assert(watcher == &socket->timeout_watcher); |
|
|
|
|
|
|
|
// printf("on_timeout\n");
|
|
|
|
|
|
|
|
if(socket->on_timeout) { socket->on_timeout(socket); } |
|
|
|
|
|
|
|
// printf("on_timeout\n");
|
|
|
|
|
|
|
|
/* TODD set timer to zero */ |
|
|
|
full_close(socket); |
|
|
|
if (socket->on_timeout) { socket->on_timeout(socket); } |
|
|
|
// timeout does not automatically kill your connection. you must!
|
|
|
|
} |
|
|
|
|
|
|
|
static void |
|
|
|
release_write_buffer(oi_socket *socket) |
|
|
|
{ |
|
|
|
while(!oi_queue_empty(&socket->out_stream)) { |
|
|
|
while (!oi_queue_empty(&socket->out_stream)) { |
|
|
|
oi_queue *q = oi_queue_last(&socket->out_stream); |
|
|
|
oi_buf *buf = oi_queue_data(q, oi_buf, queue); |
|
|
|
oi_queue_remove(q); |
|
|
|
if(buf->release) { buf->release(buf); } |
|
|
|
if (buf->release) { buf->release(buf); } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -673,57 +741,55 @@ on_io_event(EV_P_ ev_io *watcher, int revents) |
|
|
|
{ |
|
|
|
oi_socket *socket = watcher->data; |
|
|
|
|
|
|
|
if(revents & EV_ERROR) { |
|
|
|
RAISE_ERROR(socket, OI_ERROR_EV, 0); |
|
|
|
goto close; |
|
|
|
if (revents & EV_ERROR) { |
|
|
|
socket->errorno = 1; |
|
|
|
CLOSE_ASAP(socket); |
|
|
|
} |
|
|
|
|
|
|
|
int r; |
|
|
|
int have_read_event = TRUE; |
|
|
|
int have_write_event = TRUE; |
|
|
|
int have_read_event = (socket->read_action != NULL); |
|
|
|
int have_write_event = (socket->write_action != NULL); |
|
|
|
|
|
|
|
while(have_read_event || have_write_event) { |
|
|
|
|
|
|
|
if(socket->read_action) { |
|
|
|
r = socket->read_action(socket); |
|
|
|
if(r == ERROR) goto close; |
|
|
|
if(r == AGAIN) have_read_event = FALSE; |
|
|
|
} else { |
|
|
|
while (have_read_event || have_write_event) { |
|
|
|
/* RECV LOOP - TRY TO CLEAR THE BUFFER */ |
|
|
|
if (socket->read_action == NULL) |
|
|
|
have_read_event = FALSE; |
|
|
|
} |
|
|
|
else { |
|
|
|
r = socket->read_action(socket); |
|
|
|
|
|
|
|
if(socket->write_action) { |
|
|
|
r = socket->write_action(socket); |
|
|
|
if(r == ERROR) goto close; |
|
|
|
if(r == AGAIN) have_write_event = FALSE; |
|
|
|
} else { |
|
|
|
have_write_event = FALSE; |
|
|
|
if (r == AGAIN) |
|
|
|
have_read_event = FALSE; |
|
|
|
else if (r == ERROR) |
|
|
|
CLOSE_ASAP(socket); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if(socket->read_watcher.active == FALSE) |
|
|
|
have_read_event = FALSE; |
|
|
|
if(socket->write_watcher.active == FALSE) |
|
|
|
/* SEND LOOP - TRY TO CLEAR THE BUFFER */ |
|
|
|
if (socket->write_action == NULL) |
|
|
|
have_write_event = FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
if(socket->write_action == NULL && socket->read_action == NULL) |
|
|
|
goto close; |
|
|
|
else { |
|
|
|
r = socket->write_action(socket); |
|
|
|
|
|
|
|
return; |
|
|
|
if (r == AGAIN) |
|
|
|
have_write_event = FALSE; |
|
|
|
else if (r == ERROR) |
|
|
|
CLOSE_ASAP(socket); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
close: |
|
|
|
release_write_buffer(socket); |
|
|
|
// Close when both sides of the stream are closed.
|
|
|
|
if (socket->write_action == NULL && socket->read_action == NULL) { |
|
|
|
release_write_buffer(socket); |
|
|
|
|
|
|
|
ev_clear_pending (EV_A_ &socket->write_watcher); |
|
|
|
ev_clear_pending (EV_A_ &socket->read_watcher); |
|
|
|
ev_clear_pending (EV_A_ &socket->timeout_watcher); |
|
|
|
ev_clear_pending (EV_A_ &socket->write_watcher); |
|
|
|
ev_clear_pending (EV_A_ &socket->read_watcher); |
|
|
|
ev_clear_pending (EV_A_ &socket->timeout_watcher); |
|
|
|
|
|
|
|
oi_socket_detach(socket); |
|
|
|
oi_socket_detach(socket); |
|
|
|
|
|
|
|
if(socket->on_close) { socket->on_close(socket); } |
|
|
|
/* WARNING: user can free socket in on_close so no more
|
|
|
|
* access beyond this point. */ |
|
|
|
if (socket->on_close) { socket->on_close(socket); } |
|
|
|
/* WARNING: user can free socket in on_close so no more
|
|
|
|
* access beyond this point. */ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/**
|
|
|
@ -752,13 +818,17 @@ oi_socket_init(oi_socket *socket, float timeout) |
|
|
|
ev_init(&socket->read_watcher, on_io_event); |
|
|
|
socket->read_watcher.data = socket; |
|
|
|
|
|
|
|
socket->got_full_close = FALSE; |
|
|
|
socket->got_half_close = FALSE; |
|
|
|
|
|
|
|
socket->errorno = 0; |
|
|
|
|
|
|
|
socket->secure = FALSE; |
|
|
|
socket->wait_for_secure_hangup = FALSE; |
|
|
|
#if HAVE_GNUTLS |
|
|
|
socket->gnutls_errorno = 0; |
|
|
|
socket->session = NULL; |
|
|
|
#endif |
|
|
|
|
|
|
|
/* TODO higher resolution timer */ |
|
|
|
ev_timer_init(&socket->timeout_watcher, on_timeout, 0., timeout); |
|
|
|
socket->timeout_watcher.data = socket; |
|
|
|
|
|
|
@ -769,90 +839,50 @@ oi_socket_init(oi_socket *socket, float timeout) |
|
|
|
socket->on_connect = NULL; |
|
|
|
socket->on_read = NULL; |
|
|
|
socket->on_drain = NULL; |
|
|
|
socket->on_error = NULL; |
|
|
|
socket->on_timeout = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
void |
|
|
|
oi_socket_write_eof (oi_socket *socket) |
|
|
|
oi_socket_close (oi_socket *socket) |
|
|
|
{ |
|
|
|
#if HAVE_GNUTLS |
|
|
|
/* try to hang up properly for secure connections */ |
|
|
|
if(socket->secure) |
|
|
|
{ |
|
|
|
if( socket->connected /* completed handshake */ |
|
|
|
&& socket->write_action /* write end is open */ |
|
|
|
) |
|
|
|
{ |
|
|
|
socket->write_action = secure_half_goodbye; |
|
|
|
if(socket->attached) |
|
|
|
ev_io_start(SOCKET_LOOP_ &socket->write_watcher); |
|
|
|
return; |
|
|
|
} |
|
|
|
/* secure servers cannot handle half-closed connections? */ |
|
|
|
full_close(socket); |
|
|
|
return; |
|
|
|
} |
|
|
|
#endif // HAVE_GNUTLS
|
|
|
|
|
|
|
|
if(socket->write_action) |
|
|
|
half_close(socket); |
|
|
|
else |
|
|
|
full_close(socket); |
|
|
|
socket->got_half_close = TRUE; |
|
|
|
if (oi_queue_empty(&socket->out_stream)) |
|
|
|
change_state_for_empty_out_stream(socket); |
|
|
|
} |
|
|
|
|
|
|
|
void |
|
|
|
oi_socket_close (oi_socket *socket) |
|
|
|
oi_socket_full_close (oi_socket *socket) |
|
|
|
{ |
|
|
|
#if HAVE_GNUTLS |
|
|
|
/* try to hang up properly for secure connections */ |
|
|
|
if( socket->secure |
|
|
|
&& socket->connected /* completed handshake */ |
|
|
|
&& socket->write_action /* write end is open */ |
|
|
|
) |
|
|
|
{ |
|
|
|
if(socket->wait_for_secure_hangup && socket->read_action) { |
|
|
|
socket->write_action = secure_full_goodbye; |
|
|
|
socket->read_action = secure_full_goodbye; |
|
|
|
} else { |
|
|
|
socket->write_action = secure_half_goodbye; |
|
|
|
socket->read_action = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
if(socket->attached) |
|
|
|
ev_io_start(SOCKET_LOOP_ &socket->write_watcher); |
|
|
|
|
|
|
|
return; |
|
|
|
} |
|
|
|
#endif // HAVE_GNUTLS
|
|
|
|
|
|
|
|
full_close(socket); |
|
|
|
socket->got_full_close = TRUE; |
|
|
|
if (oi_queue_empty(&socket->out_stream)) |
|
|
|
change_state_for_empty_out_stream(socket); |
|
|
|
} |
|
|
|
|
|
|
|
/*
|
|
|
|
* Resets the timeout to stay alive for another socket->timeout seconds |
|
|
|
*/ |
|
|
|
void |
|
|
|
oi_socket_reset_timeout(oi_socket *socket) |
|
|
|
void oi_socket_force_close (oi_socket *socket) |
|
|
|
{ |
|
|
|
ev_timer_again(SOCKET_LOOP_ &socket->timeout_watcher); |
|
|
|
// socket->errorno = OI_SOCKET_ERROR_FORCE_CLOSE
|
|
|
|
CLOSE_ASAP(socket); |
|
|
|
} |
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes a string to the socket. This is actually sets a watcher which may |
|
|
|
* take multiple iterations to write the entire string. |
|
|
|
*/ |
|
|
|
void |
|
|
|
oi_socket_write(oi_socket *socket, oi_buf *buf) |
|
|
|
{ |
|
|
|
if(socket->write_action == NULL) |
|
|
|
return; |
|
|
|
assert(socket->write_action != NULL && "Do not write to a closed socket"); |
|
|
|
assert(socket->got_full_close == FALSE && "Do not write to a closing socket"); |
|
|
|
assert(socket->got_half_close == FALSE && "Do not write to a closing socket"); |
|
|
|
|
|
|
|
oi_queue_insert_head(&socket->out_stream, &buf->queue); |
|
|
|
|
|
|
|
buf->written = 0; |
|
|
|
// XXX if (socket->attached) ??
|
|
|
|
ev_io_start(SOCKET_LOOP_ &socket->write_watcher); |
|
|
|
|
|
|
|
if (socket->attached) { |
|
|
|
ev_io_start(SOCKET_LOOP_ &socket->write_watcher); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void |
|
|
|
oi_socket_reset_timeout(oi_socket *socket) |
|
|
|
{ |
|
|
|
ev_timer_again(SOCKET_LOOP_ &socket->timeout_watcher); |
|
|
|
} |
|
|
|
|
|
|
|
static void |
|
|
@ -886,20 +916,17 @@ oi_socket_attach(EV_P_ oi_socket *socket) |
|
|
|
|
|
|
|
ev_timer_again(EV_A_ &socket->timeout_watcher); |
|
|
|
|
|
|
|
if(socket->read_action) |
|
|
|
if (socket->read_action) |
|
|
|
ev_io_start(EV_A_ &socket->read_watcher); |
|
|
|
|
|
|
|
if(socket->write_action) |
|
|
|
if (socket->write_action) |
|
|
|
ev_io_start(EV_A_ &socket->write_watcher); |
|
|
|
|
|
|
|
/* make sure the io_event happens soon in the case we're being reattached */ |
|
|
|
ev_feed_event(EV_A_ &socket->read_watcher, EV_READ); |
|
|
|
} |
|
|
|
|
|
|
|
void |
|
|
|
oi_socket_detach(oi_socket *socket) |
|
|
|
{ |
|
|
|
if(socket->attached) { |
|
|
|
if (socket->attached) { |
|
|
|
ev_io_stop(SOCKET_LOOP_ &socket->write_watcher); |
|
|
|
ev_io_stop(SOCKET_LOOP_ &socket->read_watcher); |
|
|
|
ev_timer_stop(SOCKET_LOOP_ &socket->timeout_watcher); |
|
|
@ -920,7 +947,7 @@ oi_socket_read_stop (oi_socket *socket) |
|
|
|
void |
|
|
|
oi_socket_read_start (oi_socket *socket) |
|
|
|
{ |
|
|
|
if(socket->read_action) { |
|
|
|
if (socket->read_action) { |
|
|
|
ev_io_start(SOCKET_LOOP_ &socket->read_watcher); |
|
|
|
/* XXX feed event? */ |
|
|
|
} |
|
|
@ -933,14 +960,14 @@ oi_socket_connect(oi_socket *s, struct addrinfo *addrinfo) |
|
|
|
, addrinfo->ai_socktype |
|
|
|
, addrinfo->ai_protocol |
|
|
|
); |
|
|
|
if(fd < 0) { |
|
|
|
if (fd < 0) { |
|
|
|
perror("socket()"); |
|
|
|
return -1; |
|
|
|
} |
|
|
|
|
|
|
|
int flags = fcntl(fd, F_GETFL, 0); |
|
|
|
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
|
|
|
if(r < 0) { |
|
|
|
if (r < 0) { |
|
|
|
perror("fcntl()"); |
|
|
|
return -1; |
|
|
|
} |
|
|
@ -955,7 +982,7 @@ oi_socket_connect(oi_socket *s, struct addrinfo *addrinfo) |
|
|
|
, addrinfo->ai_addrlen |
|
|
|
); |
|
|
|
|
|
|
|
if(r < 0 && errno != EINPROGRESS) { |
|
|
|
if (r < 0 && errno != EINPROGRESS) { |
|
|
|
perror("connect"); |
|
|
|
close(fd); |
|
|
|
return -1; |
|
|
|