235 lines
6.5 KiB
C
235 lines
6.5 KiB
C
/* Copyright 2010, 2011, 2012 Tony Garnock-Jones <tonygarnockjones@gmail.com>.
|
|
*
|
|
* This file is part of Hop.
|
|
*
|
|
* Hop is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Hop is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
|
* License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Hop. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
|
|
#include <assert.h>
|
|
|
|
typedef unsigned char u_char;
|
|
#include <event.h>
|
|
|
|
#include "cmsg_private.h"
|
|
#include "harness.h"
|
|
#include "relay.h"
|
|
#include "net.h"
|
|
#include "ref.h"
|
|
#include "sexp.h"
|
|
#include "sexpio.h"
|
|
#include "hashtable.h"
|
|
#include "node.h"
|
|
#include "messages.h"
|
|
|
|
#define WANT_MESSAGE_TRACE 0
|
|
|
|
typedef struct relay_extension_t_ {
|
|
struct sockaddr_in peername;
|
|
char peername_str[256];
|
|
sexp_t *remote_container_name;
|
|
int fd;
|
|
IOHandle *outh;
|
|
} relay_extension_t;
|
|
|
|
static long connection_count = 0;
|
|
|
|
static void stats_printer(void *arg) {
|
|
while (1) {
|
|
info("%ld connections active\n", connection_count);
|
|
nap(1000);
|
|
}
|
|
}
|
|
|
|
void init_relay(void) {
|
|
spawn(stats_printer, NULL);
|
|
}
|
|
|
|
static sexp_t *relay_extend(node_t *n, sexp_t *args) {
|
|
/* TODO: outbound connections; args==NULL -> server relay, nonNULL -> outbound. */
|
|
n->extension = calloc(1, sizeof(relay_extension_t));
|
|
return NULL;
|
|
}
|
|
|
|
static void relay_destructor(node_t *n) {
|
|
relay_extension_t *r = n->extension;
|
|
delete_iohandle(r->outh);
|
|
r->outh = NULL;
|
|
if (close(r->fd) == -1) {
|
|
/* log errors as warnings here and keep on trucking */
|
|
warn("Closing file descriptor %d produced errno %d: %s\n",
|
|
r->fd, errno, strerror(errno));
|
|
}
|
|
DECREF(r->remote_container_name, sexp_destructor);
|
|
free(r);
|
|
}
|
|
|
|
static void relay_handle_message(node_t *n, sexp_t *m) {
|
|
relay_extension_t *r = n->extension;
|
|
|
|
#if WANT_MESSAGE_TRACE
|
|
info("fd %d <-- ", r->fd);
|
|
sexp_writeln(stderr_h, m);
|
|
#endif
|
|
|
|
BCHECK(!sexp_write(r->outh, m), "relay_handle_message sexp_write");
|
|
}
|
|
|
|
static node_class_t relay_class = {
|
|
.name = "relay",
|
|
.extend = relay_extend,
|
|
.destroy = relay_destructor,
|
|
.handle_message = relay_handle_message
|
|
};
|
|
|
|
static void send_error(IOHandle *h, char const *message, sexp_t *details) {
|
|
sexp_t *m = message_error(sexp_cstring(message), details);
|
|
INCREF(m);
|
|
warn("Sending error: ");
|
|
sexp_writeln(stderr_h, m);
|
|
iohandle_clear_error(h);
|
|
BCHECK(!sexp_write(h, m), "send_error sexp_write");
|
|
DECREF(m, sexp_destructor);
|
|
iohandle_flush(h); /* ignore result here, there's not much we can do with it */
|
|
}
|
|
|
|
static void send_sexp_syntax_error(IOHandle *h, char const *message) {
|
|
char const *url = "http://people.csail.mit.edu/rivest/Sexp.txt";
|
|
send_error(h, message, sexp_cstring(url));
|
|
}
|
|
|
|
static void relay_main(node_t *n) {
|
|
relay_extension_t *r = n->extension;
|
|
IOHandle *inh = new_iohandle(r->fd);
|
|
sexp_t *message = NULL; /* held */
|
|
parsed_message_t p;
|
|
|
|
INCREF(n); /* because the caller doesn't hold a ref, and we need to
|
|
drop ours on our death */
|
|
|
|
info("Accepted connection from %s on fd %d\n", r->peername_str, r->fd);
|
|
connection_count++;
|
|
|
|
iohandle_write(r->outh, cmsg_cstring_bytes("(3:hop1:0)"));
|
|
ICHECK(iohandle_flush(r->outh), "iohandle_flush greeting");
|
|
|
|
{
|
|
sexp_t *s = message_subscribe(sexp_bytes(local_container_name()),
|
|
sexp_empty_bytes, sexp_empty_bytes,
|
|
sexp_empty_bytes, sexp_empty_bytes);
|
|
INCREF(s);
|
|
sexp_write(r->outh, s);
|
|
DECREF(s, sexp_destructor);
|
|
}
|
|
|
|
//iohandle_settimeout(r->inh, 3, 0);
|
|
|
|
while (1) {
|
|
DECREF(message, sexp_destructor);
|
|
message = NULL;
|
|
if (!sexp_read(inh, &message)) goto network_error;
|
|
INCREF(message);
|
|
|
|
#if WANT_MESSAGE_TRACE
|
|
info("fd %d --> ", r->fd);
|
|
sexp_writeln(stderr_h, message);
|
|
#endif
|
|
|
|
if (parse_post(message, &p) && sexp_stringp(p.post.name)) {
|
|
cmsg_bytes_t nodename = sexp_data(p.post.name);
|
|
if (!send_node(nodename, p.post.body)) {
|
|
warn("Was asked to post to unknown node <<%.*s>>\n", nodename.len, nodename.bytes);
|
|
}
|
|
} else if (parse_subscribe(message, &p)
|
|
&& sexp_stringp(p.subscribe.filter)
|
|
&& sexp_stringp(p.subscribe.reply_sink)
|
|
&& sexp_stringp(p.subscribe.reply_name)) {
|
|
if (bind_node(sexp_data(p.subscribe.filter), n)) {
|
|
post_node(sexp_data(p.subscribe.reply_sink),
|
|
sexp_data(p.subscribe.reply_name),
|
|
message_subscribe_ok(p.subscribe.filter),
|
|
sexp_empty_bytes);
|
|
|
|
DECREF(r->remote_container_name, sexp_destructor);
|
|
r->remote_container_name = INCREF(p.subscribe.filter);
|
|
} else {
|
|
cmsg_bytes_t filter = sexp_data(p.subscribe.filter);
|
|
warn("Bind failed <<%.*s>>\n", filter.len, filter.bytes);
|
|
}
|
|
} else if (parse_unsubscribe(message, &p)
|
|
&& sexp_stringp(p.unsubscribe.token)) {
|
|
cmsg_bytes_t id = sexp_data(p.unsubscribe.token);
|
|
if (!unbind_node(id)) {
|
|
warn("Unbind failed <<%.*s>>\n", id.len, id.bytes);
|
|
}
|
|
} else {
|
|
send_error(r->outh, "message not understood", message);
|
|
goto protocol_error;
|
|
}
|
|
}
|
|
|
|
network_error:
|
|
if (inh->eof) {
|
|
info("Disconnecting fd %d normally.\n", r->fd);
|
|
} else {
|
|
switch (inh->error_kind) {
|
|
case SEXP_ERROR_OVERFLOW:
|
|
send_sexp_syntax_error(r->outh, "sexp too big");
|
|
break;
|
|
|
|
case SEXP_ERROR_SYNTAX:
|
|
send_sexp_syntax_error(r->outh, "sexp syntax error");
|
|
break;
|
|
|
|
default:
|
|
warn("Relay handle error 0x%04X on fd %d: %d, %s\n",
|
|
inh->error_kind, r->fd, inh->error_errno, strerror(inh->error_errno));
|
|
break;
|
|
}
|
|
}
|
|
|
|
protocol_error:
|
|
DECREF(message, sexp_destructor);
|
|
delete_iohandle(inh);
|
|
unbind_all_names_for_node(n);
|
|
DECREF(n, node_destructor);
|
|
|
|
connection_count--;
|
|
}
|
|
|
|
void start_relay(struct sockaddr_in const *peername, int fd) {
|
|
node_t *n = new_node(&relay_class, NULL, NULL);
|
|
relay_extension_t *r = n->extension;
|
|
r->peername = *peername;
|
|
endpoint_name(&r->peername, CMSG_BYTES(sizeof(r->peername_str), r->peername_str));
|
|
r->remote_container_name = NULL;
|
|
r->fd = fd;
|
|
r->outh = new_iohandle(r->fd);
|
|
spawn((process_main_t) relay_main, n);
|
|
}
|