Adding two new mode: STREAM_TLS, STREAM_TLS_BINARY, also add two new efun socket_set_option() and socket_get_option() for setting TLS params. (#1033)

Also update test_tls.c and /std/http.c to showcase connecting to https://www.google.com
This commit is contained in:
Yucong Sun 2023-12-22 23:54:54 -05:00 committed by GitHub
parent 3119f08ba9
commit 1fd7f61df3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1993 additions and 1501 deletions

View file

@ -53,3 +53,49 @@ SSL* tls_get_client_ctx(SSL_CTX* server_ctx) {
auto ctx = SSL_new(server_ctx);
return ctx;
}
int tls_verify_callback(int preverify_ok, X509_STORE_CTX* x509_ctx) {
char buf[256];
X509* cert;
int err, depth;
cert = X509_STORE_CTX_get_current_cert(x509_ctx);
err = X509_STORE_CTX_get_error(x509_ctx);
depth = X509_STORE_CTX_get_error_depth(x509_ctx);
X509_NAME_oneline(X509_get_subject_name(cert), buf, 256);
/* If error is not X509_V_OK, print out the error information */
if (err != X509_V_OK) {
debug(sockets, "tls_verify_callback: verify error:num=%d:%s:depth=%d:%s\n", err,
X509_verify_cert_error_string(err), depth, buf);
}
return preverify_ok;
}
SSL_CTX* tls_client_init() {
tls_library_init();
/* We MUST have entropy, or else there's no point to crypto. */
if (!RAND_poll() && !RAND_status()) return nullptr;
SSL_CTX* ctx;
ctx = SSL_CTX_new(TLS_client_method());
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1);
// Load system default CA certificates
auto ret = SSL_CTX_set_default_verify_paths(ctx);
if (ret != 1) {
debug_message("Warning: unable to load system default CA certificates.\n");
}
// setup certificate verification
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, tls_verify_callback);
return ctx;
}

View file

@ -6,3 +6,5 @@
SSL_CTX* tls_server_init(std::string_view file_cert, std::string_view file_key);
void tls_server_close(SSL_CTX* ssl_ctx);
SSL* tls_get_client_ctx(SSL_CTX* server_ctx);
SSL_CTX* tls_client_init();
int tls_verify_callback(int preverify_ok, X509_STORE_CTX* x509_ctx);

View file

@ -8,6 +8,7 @@
#include "base/package_api.h"
#include "packages/sockets/socket_efuns.h"
#include "net/tls.h"
#include <cinttypes>
#include <event2/event.h>
@ -27,51 +28,23 @@
#if defined(PACKAGE_SOCKETS) || defined(PACKAGE_EXTERNAL)
const char *socket_modes[] = {"MUD", "STREAM", "DATAGRAM", "STREAM_BINARY", "DATAGRAM_BINARY", "STREAM_TLS", "STREAM_TLS_BINARY"};
const char *socket_states[] = {"CLOSED", "CLOSING", "UNBOUND", "BOUND", "LISTEN", "HANDSHAKE", "DATA_XFER"};
const char *socket_options[] = {"SO_TLS_VERIFY_PEER", "SO_TLS_SNI_HOSTNAME"};
static char *sockaddr_to_lpcaddr(struct sockaddr *addr /*addr*/, ev_socklen_t /*len*/ len);
struct lpc_socket_event_data {
int idx;
};
namespace {
// Hold all the LPC socks
std::deque<lpc_socket_t> lpc_socks;
void on_lpc_sock_read(evutil_socket_t fd, short what, void *arg) {
debug(event, "Got an event on socket %" FMT_SOCKET_FD ":%s%s%s%s \n", fd,
(what & EV_TIMEOUT) ? " timeout" : "", (what & EV_READ) ? " read" : "",
(what & EV_WRITE) ? " write" : "", (what & EV_SIGNAL) ? " signal" : "");
auto *data = reinterpret_cast<lpc_socket_event_data *>(arg);
socket_read_select_handler(data->idx);
}
void on_lpc_sock_write(evutil_socket_t fd, short what, void *arg) {
debug(event, "Got an event on socket %" FMT_SOCKET_FD ":%s%s%s%s \n", fd,
(what & EV_TIMEOUT) ? " timeout" : "", (what & EV_READ) ? " read" : "",
(what & EV_WRITE) ? " write" : "", (what & EV_SIGNAL) ? " signal" : "");
auto *data = reinterpret_cast<lpc_socket_event_data *>(arg);
socket_write_select_handler(data->idx);
}
} // namespace
// Initialize LPC socket data structure and register events
void new_lpc_socket_event_listener(int idx, lpc_socket_t *sock, evutil_socket_t real_fd) {
auto *data = new lpc_socket_event_data;
data->idx = idx;
sock->ev_read = event_new(g_event_base, real_fd, EV_READ | EV_PERSIST, on_lpc_sock_read, data);
sock->ev_write = event_new(g_event_base, real_fd, EV_WRITE, on_lpc_sock_write, data);
sock->ev_data = data;
}
/* flags for socket_close */
#define SC_FORCE 1u
#define SC_DO_CALLBACK 2u
#define SC_FINAL_CLOSE 4u
#ifdef PACKAGE_SOCKETS
static char *sockaddr_to_lpcaddr(struct sockaddr *addr /*addr*/, ev_socklen_t /*len*/ len);
#endif
const char *error_strings[ERROR_STRINGS] = {"Problem creating socket",
"Problem with setsockopt",
"Problem setting non-blocking mode",
@ -105,6 +78,62 @@ const char *error_strings[ERROR_STRINGS] = {"Problem creating socket",
"Socket not released",
"Data nested too deeply"};
// Hold all the LPC socks
std::deque<lpc_socket_t> lpc_socks;
void on_lpc_sock_read(evutil_socket_t fd, short what, void *arg) {
debug(event, "Got an event on socket %" FMT_SOCKET_FD ":%s%s%s%s \n", fd,
(what & EV_TIMEOUT) ? " timeout" : "", (what & EV_READ) ? " read" : "",
(what & EV_WRITE) ? " write" : "", (what & EV_SIGNAL) ? " signal" : "");
auto *data = reinterpret_cast<lpc_socket_event_data *>(arg);
socket_read_select_handler(data->idx);
}
void on_lpc_sock_write(evutil_socket_t fd, short what, void *arg) {
debug(event, "Got an event on socket %" FMT_SOCKET_FD ":%s%s%s%s \n", fd,
(what & EV_TIMEOUT) ? " timeout" : "", (what & EV_READ) ? " read" : "",
(what & EV_WRITE) ? " write" : "", (what & EV_SIGNAL) ? " signal" : "");
auto *data = reinterpret_cast<lpc_socket_event_data *>(arg);
socket_write_select_handler(data->idx);
}
void handle_tls_handshake(int fd) {
auto ret = SSL_connect(lpc_socks[fd].ssl);
if (ret == 1) {
lpc_socks[fd].state = STATE_DATA_XFER;
lpc_socks[fd].flags |= S_BLOCKED;
debug(sockets, ("handle_tls_handshake: TLS: handshake successful\n"));
event_add(lpc_socks[fd].ev_write, nullptr);
return;
}
auto err = SSL_get_error(lpc_socks[fd].ssl, ret);
switch (err) {
case SSL_ERROR_WANT_READ:
// read event is persistent
return ;
case SSL_ERROR_WANT_WRITE:
event_add(lpc_socks[fd].ev_write, nullptr);
return ;
default:
debug(sockets, "STATE_HANDSHAKE: SSL_connect error: %s.\n", ERR_error_string(err, nullptr));
lpc_socks[fd].flags &= ~S_BLOCKED;
socket_close(fd, SC_FORCE | SC_FINAL_CLOSE);
return;
}
}
} // namespace
// Initialize LPC socket data structure and register events
void new_lpc_socket_event_listener(int idx, lpc_socket_t *sock, evutil_socket_t real_fd) {
auto *data = new lpc_socket_event_data;
data->idx = idx;
sock->ev_read = event_new(g_event_base, real_fd, EV_READ | EV_PERSIST, on_lpc_sock_read, data);
sock->ev_write = event_new(g_event_base, real_fd, EV_WRITE, on_lpc_sock_write, data);
sock->ev_data = data;
}
/*
* Convert a string representation of an address to a sockaddr_in
* The format of the string is "ip port", the delimiter is a whitespace.
@ -327,6 +356,7 @@ int find_new_socket() {
int socket_create(enum socket_mode mode, svalue_t *read_callback, svalue_t *close_callback) {
int type, i, fd;
int binary = 0;
bool tls = false;
if (mode == STREAM_BINARY) {
binary = 1;
@ -335,6 +365,7 @@ int socket_create(enum socket_mode mode, svalue_t *read_callback, svalue_t *clos
binary = 1;
mode = DATAGRAM;
}
switch (mode) {
case MUD:
case STREAM:
@ -343,7 +374,13 @@ int socket_create(enum socket_mode mode, svalue_t *read_callback, svalue_t *clos
case DATAGRAM:
type = SOCK_DGRAM;
break;
case STREAM_TLS_BINARY:
binary = 1;
// fall through
case STREAM_TLS:
tls = true;
type = SOCK_STREAM;
break;
default:
return EEMODENOTSUPP;
}
@ -381,8 +418,12 @@ int socket_create(enum socket_mode mode, svalue_t *read_callback, svalue_t *clos
#ifndef _WIN32
// Set send buffer to 256K
{
int sendbuf = 256 * 1024;
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof(sendbuf)) == -1) {
int size = 256 * 1024;
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) == -1) {
debug(sockets, "socket_create: setsockopt so_sndbuf error: %s.\n",
evutil_socket_error_to_string(evutil_socket_geterror(fd)));
}
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) == -1) {
debug(sockets, "socket_create: setsockopt so_sndbuf error: %s.\n",
evutil_socket_error_to_string(evutil_socket_geterror(fd)));
}
@ -404,12 +445,16 @@ int socket_create(enum socket_mode mode, svalue_t *read_callback, svalue_t *clos
if (binary) {
lpc_socks[i].flags |= S_BINARY;
}
if (tls) {
lpc_socks[i].flags |= S_TLS_SUPPORT;
}
lpc_socks[i].mode = mode;
lpc_socks[i].state = STATE_UNBOUND;
lpc_socks[i].owner_ob = current_object;
current_object->flags |= O_EFUN_SOCKET;
debug(sockets, "socket_create: created lpc socket %d (real fd %d) mode %d\n", i, fd, mode);
debug(sockets, "socket_create: created lpc socket %d (real fd %d) mode %s\n", i, fd, socket_modes[mode]);
}
return i;
@ -672,6 +717,7 @@ int socket_connect(int fd, const char *name, svalue_t *read_callback, svalue_t *
break;
case STATE_LISTEN:
return EEISLISTEN;
case STATE_HANDSHAKE:
case STATE_DATA_XFER:
return EEISCONN;
}
@ -720,14 +766,44 @@ int socket_connect(int fd, const char *name, svalue_t *read_callback, svalue_t *
}
}
lpc_socks[fd].state = STATE_DATA_XFER;
lpc_socks[fd].flags |= S_BLOCKED;
if (lpc_socks[fd].flags & S_TLS_SUPPORT) {
auto *ssl_ctx = tls_client_init();
if (ssl_ctx == nullptr) {
debug(sockets, "socket_connect: tls_client_init error.\n");
return EECONNECT;
}
lpc_socks[fd].ssl_ctx = ssl_ctx;
auto *ssl = SSL_new(ssl_ctx);
lpc_socks[fd].ssl = ssl;
SSL_set_fd(ssl, lpc_socks[fd].fd);
if (lpc_socks[fd].options[SO_TLS_VERIFY_PEER].type == T_NUMBER &&
lpc_socks[fd].options[SO_TLS_VERIFY_PEER].u.number != 0) {
SSL_set_verify(ssl, SSL_VERIFY_PEER, tls_verify_callback);
} else {
SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
}
if (lpc_socks[fd].options[SO_TLS_SNI_HOSTNAME].type == T_STRING) {
SSL_set_tlsext_host_name(ssl, lpc_socks[fd].options[SO_TLS_SNI_HOSTNAME].u.string);
}
}
event_add(lpc_socks[fd].ev_read, nullptr);
event_add(lpc_socks[fd].ev_write, nullptr);
debug(sockets, "socket_connect: lpc socket %d (real fd %" FMT_SOCKET_FD ") connecting.\n", fd,
lpc_socks[fd].fd);
event_add(lpc_socks[fd].ev_read, nullptr);
event_add(lpc_socks[fd].ev_write, nullptr);
if (lpc_socks[fd].flags & S_TLS_SUPPORT) {
lpc_socks[fd].state = STATE_HANDSHAKE;
handle_tls_handshake(fd);
} else {
lpc_socks[fd].state = STATE_DATA_XFER;
lpc_socks[fd].flags |= S_BLOCKED;
}
return EESUCCESS;
}
@ -774,7 +850,7 @@ int socket_write(int fd, svalue_t *message, const char *name) {
switch (lpc_socks[fd].mode) {
case MUD:
debug(sockets, "socket_write: sending tcp message to %s\n",
debug(sockets, "socket_write: sending MUD message to %s\n",
sockaddr_to_string((struct sockaddr *)&lpc_socks[fd].r_addr, lpc_socks[fd].r_addrlen));
switch (message->type) {
case T_OBJECT:
@ -799,8 +875,11 @@ int socket_write(int fd, svalue_t *message, const char *name) {
}
break;
case STREAM_BINARY:
case STREAM_TLS:
case STREAM_TLS_BINARY:
case STREAM:
debug(sockets, "socket_write: sending tcp message to %s\n",
debug(sockets, "socket_write: sending TCP message to %s\n",
sockaddr_to_string((struct sockaddr *)&lpc_socks[fd].r_addr, lpc_socks[fd].r_addrlen));
switch (message->type) {
case T_BUFFER:
@ -862,8 +941,9 @@ int socket_write(int fd, svalue_t *message, const char *name) {
}
break;
case DATAGRAM_BINARY:
case DATAGRAM:
debug(sockets, "socket_write: sending udp message to %s\n",
debug(sockets, "socket_write: sending UDP message to %s\n",
sockaddr_to_string((struct sockaddr *)&addr, addrlen));
switch (message->type) {
case T_STRING:
@ -909,26 +989,65 @@ int socket_write(int fd, svalue_t *message, const char *name) {
return EESUCCESS;
}
debug(sockets, "socket_write: message size %d.\n", len);
off = send(lpc_socks[fd].fd, buf, len, 0);
if (off <= 0) {
auto e = evutil_socket_geterror(lpc_socks[fd].fd);
FREE(buf);
if (off == -1) {
auto is_tls = (lpc_socks[fd].mode == STREAM_TLS || lpc_socks[fd].mode == STREAM_TLS_BINARY);
bool should_continue = false;
if (is_tls) {
off = SSL_write(lpc_socks[fd].ssl, buf, len);
} else {
off = send(lpc_socks[fd].fd, buf, len, 0);
}
if(off > 0) {
should_continue = true;
} else if (off <= 0) {
if(is_tls) {
auto e = SSL_get_error(lpc_socks[fd].ssl, off);
switch (e) {
case ERR(EWOULDBLOCK): {
debug(sockets, "socket_write: write would block.\n");
return EEWOULDBLOCK;
}
case ERR(EINTR): {
debug(sockets, "socket_write: write interrupted.\n");
return EEINTR;
}
case SSL_ERROR_WANT_READ:
debug(sockets, "socket_write: SSL_ERROR_WANT_READ.\n");
should_continue = true;
break;
case SSL_ERROR_WANT_WRITE:
debug(sockets, "socket_write: SSL_ERROR_WANT_WRITE.\n");
should_continue = true;
break;
case SSL_ERROR_SYSCALL:
switch (errno) {
case EWOULDBLOCK:
debug(sockets, "socket_write: EWOULDBLOCK.\n");
should_continue = true;
break;
case EINTR:
debug(sockets, "socket_write: EINTR.\n");
should_continue = true;
break;
}
default:
debug(sockets, "ssl_write: lpc socket %d (real fd %" FMT_SOCKET_FD ") error: %s.\n",
fd, lpc_socks[fd].fd, ERR_error_string(e, nullptr));
debug(sockets, "ssl error: %s\n", ERR_error_string(ERR_get_error(), nullptr));
break;
}
}
debug(sockets, "socket_write: lpc socket %d (real fd %" FMT_SOCKET_FD ") send error: %s.\n", fd,
lpc_socks[fd].fd, evutil_socket_error_to_string(e));
} else {
auto e = evutil_socket_geterror(lpc_socks[fd].fd);
switch (e) {
case ERR(EWOULDBLOCK): {
debug(sockets, "socket_write: write would block.\n");
return EEWOULDBLOCK;
}
case ERR(EINTR): {
debug(sockets, "socket_write: write interrupted.\n");
return EEINTR;
}
}
debug(sockets, "socket_write: lpc socket %d (real fd %" FMT_SOCKET_FD ") send error: %s.\n",
fd, lpc_socks[fd].fd, evutil_socket_error_to_string(e));
}
}
if (!should_continue) {
lpc_socks[fd].flags |= S_LINKDEAD;
socket_close(fd, SC_FORCE | SC_DO_CALLBACK | SC_FINAL_CLOSE);
FREE(buf);
return EESEND;
}
@ -997,7 +1116,7 @@ void socket_read_select_handler(int fd) {
struct sockaddr_storage sockaddr;
socklen_t addrlen;
debug(sockets, "read_socket_handler: fd %d state %d\n", fd, lpc_socks[fd].state);
debug(sockets, "read_socket_handler: fd %d state %s\n", fd, socket_states[lpc_socks[fd].state]);
switch (lpc_socks[fd].state) {
case STATE_CLOSED:
@ -1010,10 +1129,7 @@ void socket_read_select_handler(int fd) {
case STATE_BOUND:
switch (lpc_socks[fd].mode) {
case MUD:
case STREAM:
break;
case DATAGRAM_BINARY:
case DATAGRAM: {
char addr[NI_MAXHOST + NI_MAXSERV];
debug(sockets, ("read_socket_handler: DATA_XFER DATAGRAM\n"));
@ -1066,8 +1182,12 @@ void socket_read_select_handler(int fd) {
call_callback(fd, S_READ_FP, 3);
return;
}
case MUD:
case STREAM:
case STREAM_BINARY:
case DATAGRAM_BINARY:;
case STREAM_TLS:
case STREAM_TLS_BINARY:
break;
}
break;
@ -1078,6 +1198,19 @@ void socket_read_select_handler(int fd) {
call_callback(fd, S_READ_FP, 1);
return;
case STATE_HANDSHAKE: {
switch (lpc_socks[fd].mode) {
case STREAM_TLS:
case STREAM_TLS_BINARY: {
debug(sockets, ("read_socket_handler: HANDSHAKE\n"));
return handle_tls_handshake(fd);
}
default:
debug(sockets, ("read_socket_handler: STATE_HANDSHAKE unsupported mode.\n"));
break;
}
}
case STATE_DATA_XFER:
switch (lpc_socks[fd].mode) {
case DATAGRAM:
@ -1194,31 +1327,86 @@ void socket_read_select_handler(int fd) {
call_callback(fd, S_READ_FP, 2);
return;
case STREAM_BINARY:
case DATAGRAM_BINARY:;
case DATAGRAM_BINARY:
break;
case STREAM_TLS:
case STREAM_TLS_BINARY:
debug(sockets, ("read_socket_handler: DATA_XFER STREAM_TLS\n"));
cc = SSL_read(lpc_socks[fd].ssl, buf, sizeof(buf) - 1);
if (cc <= 0) {
auto err = SSL_get_error(lpc_socks[fd].ssl, cc);
switch (err) {
case SSL_ERROR_WANT_READ:
// do nothing, wait for next read event
return;
default:
debug(sockets, "read_socket_handler: SSL_read error: %s.\n", ERR_error_string(err, nullptr));
debug(sockets, "SSL error: %s.\n", ERR_error_string(ERR_get_error(), nullptr));
// will close connection at the end.
break;
}
break;
}
#ifdef F_NETWORK_STATS
if (!(lpc_socks[fd].flags & S_EXTERNAL)) {
inet_in_packets++;
inet_in_volume += cc;
inet_socket_in_packets++;
inet_socket_in_volume += cc;
}
#endif
debug(sockets, "read_socket_handler: SSL_read %d bytes\n", cc);
buf[cc] = '\0';
push_number(fd);
if (lpc_socks[fd].flags & S_BINARY) {
buffer_t *b;
b = allocate_buffer(cc);
if (b) {
b->ref--;
memcpy(b->item, buf, cc);
push_buffer(b);
} else {
push_number(0);
}
} else {
auto res = u8_sanitize(buf);
copy_and_push_string(res.c_str());
}
debug(sockets, ("read_socket_handler: apply read callback\n"));
call_callback(fd, S_READ_FP, 2);
return;
}
break;
}
if (cc == -1) {
auto e = evutil_socket_geterror(lpc_socks[fd].fd);
debug(sockets, "read_socket_handler: %d (fd %d), error: (%d) %s.\n", fd, lpc_socks[fd].fd, e,
evutil_socket_error_to_string(e));
switch (e) {
case ERR(ECONNREFUSED):
/* Evidentally, on Linux 1.2.1, ECONNREFUSED gets returned
switch(lpc_socks[fd].mode) {
case MUD:
case STREAM:
if (cc == -1) {
auto e = evutil_socket_geterror(lpc_socks[fd].fd);
debug(sockets, "read_socket_handler: %d (fd %d), error: (%d) %s.\n", fd, lpc_socks[fd].fd,
e, evutil_socket_error_to_string(e));
switch (e) {
case ERR(ECONNREFUSED):
/* Evidentally, on Linux 1.2.1, ECONNREFUSED gets returned
* if an ICMP_PORT_UNREACHED error happens internally. Why
* they use this error message, I have no idea, but this seems
* to work.
*/
if (lpc_socks[fd].state == STATE_BOUND && lpc_socks[fd].mode == DATAGRAM) {
return;
*/
if (lpc_socks[fd].state == STATE_BOUND && lpc_socks[fd].mode == DATAGRAM) {
return;
}
break;
case ERR(EINTR):
case ERR(EWOULDBLOCK):
return;
default:
break;
}
break;
case ERR(EINTR):
case ERR(EWOULDBLOCK):
return;
default:
break;
}
}
default:
break;
}
lpc_socks[fd].flags |= S_LINKDEAD;
@ -1231,8 +1419,22 @@ void socket_read_select_handler(int fd) {
void socket_write_select_handler(int fd) {
int cc;
debug(sockets, "write_socket_handler: lpc socket %d (real fd %" FMT_SOCKET_FD ") state %d\n", fd,
lpc_socks[fd].fd, lpc_socks[fd].state);
debug(sockets, "write_socket_handler: lpc socket %d (real fd %" FMT_SOCKET_FD ") state %s\n", fd,
lpc_socks[fd].fd, socket_states[lpc_socks[fd].state]);
if (lpc_socks[fd].state == STATE_HANDSHAKE) {
switch (lpc_socks[fd].mode) {
case STREAM_TLS:
case STREAM_TLS_BINARY: {
debug(sockets, ("socket_write_select_handler: HANDSHAKE\n"));
handle_tls_handshake(fd);
return ;
}
default:
debug(sockets, ("socket_write_select_handler: STATE_HANDSHAKE unsupported mode.\n"));
break;
}
}
/* if the socket isn't blocked, we've got nothing to send */
/* if the socket is linkdead, don't send -- could block */
@ -1271,26 +1473,52 @@ void socket_write_select_handler(int fd) {
}
if (lpc_socks[fd].w_buf != nullptr) {
cc = send(lpc_socks[fd].fd, lpc_socks[fd].w_buf + lpc_socks[fd].w_off, lpc_socks[fd].w_len, 0);
if (cc < 0) {
auto e = evutil_socket_geterror(lpc_socks[fd].fd);
if (cc == -1 && (e == ERR(EWOULDBLOCK) || e == EAGAIN /* linux only */ || e == ERR(EINTR))) {
event_add(lpc_socks[fd].ev_write, nullptr);
if (lpc_socks[fd].mode == STREAM_TLS || lpc_socks[fd].mode == STREAM_TLS_BINARY) {
cc = SSL_write(lpc_socks[fd].ssl, lpc_socks[fd].w_buf + lpc_socks[fd].w_off,
lpc_socks[fd].w_len);
if (cc <= 0) {
auto err = SSL_get_error(lpc_socks[fd].ssl, cc);
switch (err) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
event_add(lpc_socks[fd].ev_write, nullptr);
break;
default:
debug(sockets, "write_socket_handler: SSL_write error: %s.\n", ERR_error_string(err, nullptr));
debug(sockets, "SSL Error: %s", ERR_error_string(ERR_get_error(), nullptr));
lpc_socks[fd].flags |= S_LINKDEAD;
if (lpc_socks[fd].state == STATE_FLUSHING) {
lpc_socks[fd].flags &= ~S_BLOCKED;
socket_close(fd, SC_FORCE | SC_FINAL_CLOSE);
} else {
socket_close(fd, SC_FORCE | SC_DO_CALLBACK | SC_FINAL_CLOSE);
}
}
return;
}
debug(sockets,
"write_socket_handler: lpc_socket %d (real fd %" FMT_SOCKET_FD
") write failed: "
"%s, connection dead.\n",
fd, lpc_socks[fd].fd, evutil_socket_error_to_string(e));
lpc_socks[fd].flags |= S_LINKDEAD;
if (lpc_socks[fd].state == STATE_FLUSHING) {
lpc_socks[fd].flags &= ~S_BLOCKED;
socket_close(fd, SC_FORCE | SC_FINAL_CLOSE);
} else {
cc = send(lpc_socks[fd].fd, lpc_socks[fd].w_buf + lpc_socks[fd].w_off, lpc_socks[fd].w_len, 0);
if (cc < 0) {
auto e = evutil_socket_geterror(lpc_socks[fd].fd);
if (cc == -1 &&
(e == ERR(EWOULDBLOCK) || e == EAGAIN /* linux only */ || e == ERR(EINTR))) {
event_add(lpc_socks[fd].ev_write, nullptr);
return;
}
debug(sockets,
"write_socket_handler: lpc_socket %d (real fd %" FMT_SOCKET_FD
") write failed: "
"%s, connection dead.\n",
fd, lpc_socks[fd].fd, evutil_socket_error_to_string(e));
lpc_socks[fd].flags |= S_LINKDEAD;
if (lpc_socks[fd].state == STATE_FLUSHING) {
lpc_socks[fd].flags &= ~S_BLOCKED;
socket_close(fd, SC_FORCE | SC_FINAL_CLOSE);
return;
}
socket_close(fd, SC_FORCE | SC_DO_CALLBACK | SC_FINAL_CLOSE);
return;
}
socket_close(fd, SC_FORCE | SC_DO_CALLBACK | SC_FINAL_CLOSE);
return;
}
#ifdef F_NETWORK_STATS
if (!(lpc_socks[fd].flags & S_EXTERNAL)) {
@ -1303,13 +1531,17 @@ void socket_write_select_handler(int fd) {
lpc_socks[fd].w_off += cc;
lpc_socks[fd].w_len -= cc;
if (lpc_socks[fd].w_len != 0) {
// trigger send again
event_add(lpc_socks[fd].ev_write, nullptr);
return;
}
FREE(lpc_socks[fd].w_buf);
lpc_socks[fd].w_buf = nullptr;
lpc_socks[fd].w_off = 0;
}
lpc_socks[fd].flags &= ~S_BLOCKED;
if (lpc_socks[fd].state == STATE_FLUSHING) {
socket_close(fd, SC_FORCE | SC_FINAL_CLOSE);
return;
@ -1319,6 +1551,12 @@ void socket_write_select_handler(int fd) {
push_number(fd);
call_callback(fd, S_WRITE_FP, 1);
// trigger send again
if (lpc_socks[fd].w_len != 0) {
event_add(lpc_socks[fd].ev_write, nullptr);
return;
}
}
/*
@ -1344,6 +1582,15 @@ int socket_close(int fd, int flags) {
call_callback(fd, S_CLOSE_FP, 1);
}
if (lpc_socks[fd].ssl != nullptr) {
SSL_shutdown(lpc_socks[fd].ssl);
// TODO: process write
SSL_free(lpc_socks[fd].ssl);
lpc_socks[fd].ssl = nullptr;
SSL_CTX_free(lpc_socks[fd].ssl_ctx);
lpc_socks[fd].ssl_ctx = nullptr;
}
set_read_callback(fd, nullptr);
set_write_callback(fd, nullptr);
set_close_callback(fd, nullptr);
@ -1574,10 +1821,6 @@ static char *sockaddr_to_lpcaddr(struct sockaddr *addr, socklen_t len) {
return result;
}
const char *socket_modes[] = {"MUD", "STREAM", "DATAGRAM", "STREAM_BINARY", "DATAGRAM_BINARY"};
const char *socket_states[] = {"CLOSED", "CLOSING", "UNBOUND", "BOUND", "LISTEN", "DATA_XFER"};
/*
* Return an array containing info for a socket
*/

View file

@ -9,7 +9,24 @@
#include <event2/util.h>
enum socket_mode { MUD, STREAM, DATAGRAM, STREAM_BINARY, DATAGRAM_BINARY };
// The number here is same as the one in include/socket.h
enum socket_mode {
MUD = 0,
STREAM = 1,
DATAGRAM = 2,
STREAM_BINARY = 3,
DATAGRAM_BINARY = 4,
STREAM_TLS = 5,
STREAM_TLS_BINARY = 6
};
enum socket_option {
SO_INVALID = 0,
SO_TLS_VERIFY_PEER = 1,
SO_TLS_SNI_HOSTNAME = 2,
};
constexpr int NUM_SOCKET_OPTIONS = 3;
enum socket_state {
STATE_CLOSED,
@ -17,10 +34,11 @@ enum socket_state {
STATE_UNBOUND,
STATE_BOUND,
STATE_LISTEN,
STATE_HANDSHAKE,
STATE_DATA_XFER
};
#define BUF_SIZE 8192 /* max reliable packet size */
#define BUF_SIZE 65535 /* max reliable packet size */
#ifdef IPV6
#define ADDR_BUF_SIZE INET6_ADDRSTRLEN
#else
@ -49,18 +67,24 @@ struct lpc_socket_t {
struct event *ev_read;
struct event *ev_write;
struct lpc_socket_event_data *ev_data;
SSL_CTX* ssl_ctx;
SSL* ssl;
svalue_t options[NUM_SOCKET_OPTIONS];
};
#define S_RELEASE 0x001
#define S_BLOCKED 0x002
#define S_HEADER 0x004
#define S_WACCEPT 0x008
#define S_BINARY 0x010
#define S_READ_FP 0x020
#define S_WRITE_FP 0x040
#define S_CLOSE_FP 0x080
#define S_EXTERNAL 0x100
#define S_LINKDEAD 0x200
enum socket_flags {
S_RELEASE = 0x001,
S_BLOCKED = 0x002,
S_HEADER = 0x004,
S_WACCEPT = 0x008,
S_BINARY = 0x010,
S_READ_FP = 0x020,
S_WRITE_FP = 0x040,
S_CLOSE_FP = 0x080,
S_EXTERNAL = 0x100,
S_LINKDEAD = 0x200,
S_TLS_SUPPORT = 0x400,
};
array_t *socket_status(int);
array_t *socket_status_by_fd(int);

View file

@ -299,4 +299,61 @@ void f_socket_status() {
}
}
#ifdef F_SOCKET_SET_OPTION
void f_socket_set_option() {
auto lpc_sock = (sp - 2)->u.number;
auto option = (sp - 1)->u.number;
auto *arg = sp;
if (lpc_sock < 0 || lpc_sock >= lpc_socks_num()) {
error("Bad socket descriptor: %d\n", lpc_sock);
}
switch(option) {
case SO_TLS_VERIFY_PEER:
if (arg->type != T_NUMBER) {
bad_arg(3, F_SOCKET_SET_OPTION);
}
if (arg->u.number != 0 && arg->u.number != 1) {
error("Bad socket option value: %d, onl 0 or 1 is accepted.\n", arg->u.number);
}
assign_svalue_no_free(&lpc_socks_get(lpc_sock)->options[SO_TLS_VERIFY_PEER], arg);
break;
case SO_TLS_SNI_HOSTNAME:
if (arg->type != T_STRING) {
bad_arg(3, F_SOCKET_SET_OPTION);
}
assign_svalue_no_free(&lpc_socks_get(lpc_sock)->options[SO_TLS_SNI_HOSTNAME], arg);
break;
default:
error("Unknown socket option: %d\n", option);
}
pop_3_elems();
}
#endif
#ifdef F_SOCKET_GET_OPTION
void f_socket_get_option() {
auto lpc_sock = (sp - 2)->u.number;
auto option = (sp - 1)->u.number;
if (lpc_sock < 0 || lpc_sock >= lpc_socks_num()) {
error("Bad socket descriptor: %d\n", lpc_sock);
}
switch(option) {
case SO_TLS_VERIFY_PEER:
push_number(lpc_socks_get(lpc_sock)->options[SO_TLS_VERIFY_PEER].u.number);
break;
case SO_TLS_SNI_HOSTNAME:
copy_and_push_string(lpc_socks_get(lpc_sock)->options[SO_TLS_SNI_HOSTNAME].u.string);
break;
default:
error("Unknown socket option: %d\n", option);
}
pop_2_elems();
}
#endif
#endif

View file

@ -13,7 +13,8 @@
int socket_acquire(int, string | function, string | function, string | function);
string socket_error(int);
string socket_address(int | object, int default: 0);
void socket_set_option(int, int, mixed);
mixed socket_get_option(int, int);
/*
* ret[0] = (int) fd
* ret[1] = (string) state

View file

@ -739,7 +739,7 @@ void SSL_CTX_set_verify_depth(SSL_CTX *ctx, int depth);
*
* @return verifying result
*/
int verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx);
int tls_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx);
/**
* @brief set the session timeout time

View file

@ -73,7 +73,7 @@ OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) &&
wsi->tls.use_ssl & LCCSCF_ALLOW_SELFSIGNED) {
lwsl_notice("accepting self-signed "
"certificate (verify_callback)\n");
"certificate (tls_verify_callback)\n");
X509_STORE_CTX_set_error(x509_ctx, X509_V_OK);
return 1; // ok
} else if ((err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY ||

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
#define STD_HTTP "/std/http.c"
object tp;
void callback(object tp, string result)
{
tell_object(tp, "Result: \n" + result + "\n");
}
int main(string arg)
{
tp = this_player();
STD_HTTP->get("www.google.com", 443, "/", 1, (: callback(tp, $1) :));
return 1;
}

View file

@ -3,4 +3,6 @@ void do_tests() {
ASSERT_EQ(9, strwidth("what e\nver"));
ASSERT_EQ(10 + 2 + 10, strwidth("欲穷千里目👩‍👩‍👧‍👧更上一层楼"));
ASSERT_EQ(4, strwidth("\uD83E\uDD26\uD83C\uDFFB\u200D\uFE0F"));
}

View file

@ -36,12 +36,17 @@
#define STREAM 1
#define DATAGRAM 2
#define STREAM_TLS 5
#define STATE_RESOLVING 0
#define STATE_CONNECTING 1
#define STATE_CLOSED 2
#define STATE_CONNECTED 3
#define SO_TLS_VERIFY_PEER 1
#define SO_TLS_SNI_HOSTNAME 2
nosave mapping hostname_to_fd;
nosave mapping status;
nosave object receiver;
@ -53,18 +58,17 @@ void create() {
void socket_shutdown(int fd) {
status[fd]["status"] = STATE_CLOSED;
tell_object(receiver, sprintf("%O", status));
evaluate(status[fd]["callback"], status[fd]["result"]);
}
void receive_data(int fd, mixed result) {
tell_object(receiver, sprintf("fd %d received: %O", fd, result));
tell_object(receiver, sprintf("fd %d received %d bytes.\n", fd, sizeof(result)));
status[fd]["result"] += result;
}
void write_data(int fd) {
status[fd]["status"] = STATE_CONNECTED;
tell_object(receiver, sprintf("%O", status));
tell_object(receiver, sprintf("write_data: %O\n", status));
socket_write(fd, "GET " + status[fd]["path"] + " HTTP/1.0\nHost: " + status[fd]["host"] + "\n\r\n\r");
}
@ -87,25 +91,35 @@ void on_resolve(string host, string addr, int key) {
}
status[fd]["status"] = STATE_CONNECTING;
tell_object(receiver, sprintf("%O", socket_status(fd)));
tell_object(receiver, sprintf("on_resolve: %O\n", socket_status(fd)));
}
}
int get(string host, int port, string path) {
int get(string host, int port, string path, int tls, mixed callback) {
int ret;
int fd;
receiver = this_player();
fd = socket_create(STREAM, "receive_data", "socket_shutdown");
fd = socket_create(tls ? STREAM_TLS : STREAM, "receive_data", "socket_shutdown");
if (tls) {
socket_set_option(fd, SO_TLS_VERIFY_PEER, 1);
socket_set_option(fd, SO_TLS_SNI_HOSTNAME, host);
}
status[fd] = ([]);
status[fd]["status"] = STATE_RESOLVING;
status[fd]["host"] = host;
status[fd]["port"] = port;
status[fd]["path"] = path;
status[fd]["result"] = "";
status[fd]["caller"] = previous_object();
status[fd]["callback"] = bind(callback, previous_object());
tell_object(receiver, sprintf("%O", status));
tell_object(receiver, sprintf("get: %O\n", status));
hostname_to_fd[host] = fd;
resolve(host, "on_resolve");
return 0;