You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

939 lines
21 KiB

/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2015
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <dml/dml_server.h>
#include <dml/dml_connection.h>
#include <dml/dml_packet.h>
#include <dml/dml_route.h>
#include <dml/dml.h>
#include "dml_config.h"
#include <dml/dml_client.h>
#include <dml/dml_id.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
static bool debug = false;
#define DMLD_DATA_KEEPALIVE 60
struct connection_update {
uint8_t id[DML_ID_SIZE];
uint8_t hops;
struct connection_update *next;
};
struct connection_data_client {
struct dml_connection *dc;
uint16_t packet_id;
struct connection_data_client *next;
};
struct connection_data {
uint8_t id[DML_ID_SIZE];
struct dml_connection *dc;
uint16_t packet_id;
time_t t_data;
struct connection_data_client *client_list;
struct connection_data *next;
};
struct connection_data *data_list;
int connection_data_remove(struct connection_data *data);
int connection_data_remove_client(struct connection_data *data, struct dml_connection *dc);
void connection_data_update(struct dml_connection *dc, bool old_valid);
struct connection {
struct dml_connection *dc;
uint32_t flags;
struct dml_client *client;
uint8_t update_id[DML_ID_SIZE];
struct connection_update *bad_list;
struct connection_update *good_list;
struct connection_update *req_description;
struct connection_update *req_certificate;
struct connection_update *req_header;
char *name;
struct connection *next;
};
struct connection *connection_list = NULL;
struct connection *connection_create(void)
{
struct connection *con;
con = calloc(1, sizeof(struct connection));
if (!con)
goto err_calloc;
con->name = strdup("unknown connection");
if (!con->name)
goto err_name;
con->next = connection_list;
connection_list = con;
return con;
err_name:
free(con);
err_calloc:
return NULL;
}
char *connection_name_get(struct connection *con)
{
return con->name;
}
int connection_name_set(struct connection *con, char *name)
{
char *tmp = strdup(name);
if (!tmp)
return -1;
free(con->name);
con->name = tmp;
return 0;
}
void connection_destroy(struct connection *con)
{
struct connection **entry;
for (entry = &connection_list; *entry; entry = &(*entry)->next) {
if (*entry == con) {
*entry = con->next;
break;
}
}
g_source_remove_by_user_data(con);
g_source_remove_by_user_data(con);
while (con->bad_list) {
struct connection_update *cu = con->bad_list;
con->bad_list = cu->next;
free(cu);
}
while (con->good_list) {
struct connection_update *cu = con->good_list;
con->good_list = cu->next;
free(cu);
}
while (con->req_description) {
struct connection_update *cu = con->req_description;
con->req_description = cu->next;
free(cu);
}
while (con->req_certificate) {
struct connection_update *cu = con->req_certificate;
con->req_certificate = cu->next;
free(cu);
}
while (con->req_header) {
struct connection_update *cu = con->req_header;
con->req_header = cu->next;
free(cu);
}
connection_data_update(con->dc, false);
struct connection_data *data, *dnext;
printf("remove client %s from data list\n", con->name);
for (data = data_list; data; data = dnext) {
dnext = data->next;
connection_data_remove_client(data, con->dc);
if (!data->client_list && data->dc != con->dc) {
printf("Sending disconnect request upstream\n");
dml_packet_send_req_disc(data->dc, data->id);
connection_data_remove(data);
}
}
free(con->name);
free(con);
}
struct connection_data *connection_data_create(void)
{
struct connection_data *entry;
entry = calloc(1, sizeof(struct connection_data));
if (!entry)
return NULL;
entry->next = data_list;
data_list = entry;
return entry;
}
struct connection_data *connection_data_by_connection(struct dml_connection *dc, uint16_t packet_id)
{
struct connection_data *entry;
for (entry = data_list; entry; entry = entry->next) {
if (entry->dc == dc && entry->packet_id == packet_id) {
return entry;
}
}
return NULL;
}
struct connection_data *connection_data_by_id(uint8_t id[DML_ID_SIZE])
{
struct connection_data *entry;
for (entry = data_list; entry; entry = entry->next) {
if (!memcmp(entry->id, id, DML_ID_SIZE)) {
return entry;
}
}
return NULL;
}
int connection_data_add_client(struct connection_data *data, struct dml_connection *dc, uint16_t packet_id)
{
struct connection_data_client *entry;
struct connection *con = dml_connection_arg_get(dc);
for (entry = data->client_list; entry; entry = entry->next) {
if (entry->dc == dc)
break;
}
if (!entry) {
entry = calloc(1, sizeof(struct connection_data_client));
if (!entry)
return -1;
entry->next = data->client_list;
data->client_list = entry;
}
char *idstr = dml_id_str(data->id);
printf("Add client to %s: %s %d\n", idstr, connection_name_get(con), packet_id);
free(idstr);
entry->packet_id = packet_id;
entry->dc = dc;
return 0;
}
int connection_data_remove_client(struct connection_data *data, struct dml_connection *dc)
{
struct connection_data_client **entry;
struct connection *con = dml_connection_arg_get(dc);
char *idstr = dml_id_str(data->id);
printf("Remove client from %s: %s\n", idstr, connection_name_get(con));
free(idstr);
for (entry = &data->client_list; *entry; entry = &(*entry)->next) {
struct connection_data_client *old = *entry;
if (old->dc != dc)
continue;
*entry = old->next;
free(old);
return 0;
}
return -1;
}
uint16_t connection_data_new_id(void)
{
static uint16_t id = DML_PACKET_DATA;
struct connection_data *entry;
id++;
do {
for (entry = data_list; entry; entry = entry->next) {
if (entry->packet_id == id) {
id++;
if (id < DML_PACKET_DATA)
id = DML_PACKET_DATA;
break;
}
}
if (!entry)
return id;
} while(1);
}
int connection_data_remove(struct connection_data *data)
{
struct connection_data **entry;
char *idstr = dml_id_str(data->id);
printf("Removing %s from data list\n", idstr);
free(idstr);
for (entry = &data_list; *entry; entry = &(*entry)->next) {
if (*entry == data) {
*entry = data->next;
break;
}
}
while (data->client_list) {
struct connection_data_client *dc = data->client_list;
dml_packet_send_disc(dc->dc, data->id, DML_PACKET_DISC_UNROUTABLE);
data->client_list = dc->next;
free(dc);
}
free(data);
return 0;
}
gboolean connection_data_keepalive(void *arg)
{
time_t now = time(NULL);
struct connection_data *entry;
for (entry = data_list; entry; entry = entry->next) {
if (now > entry->t_data + DMLD_DATA_KEEPALIVE) {
char *idstr = dml_id_str(entry->id);
struct connection *con = dml_connection_arg_get(entry->dc);
printf("No data for a while, sending keepalive connect %s to %s\n",
idstr, connection_name_get(con));
free(idstr);
dml_packet_send_connect(entry->dc, entry->id, entry->packet_id);
entry->t_data = now;
}
}
g_timeout_add_seconds(DMLD_DATA_KEEPALIVE, connection_data_keepalive, connection_data_keepalive);
return G_SOURCE_REMOVE;
}
/* A connection has been updated, recheck */
void connection_data_update(struct dml_connection *dc, bool old_valid)
{
struct connection_data *data, *dnext;
for (data = data_list; data; data = dnext) {
dnext = data->next;
/* Is it using the connection? */
if (data->dc != dc)
continue;
struct dml_connection *dc_r = dml_route_connection_get(data->id);
if (dc_r) {
if (dc_r != dc) {
data->dc = dc_r;
dml_packet_send_connect(dc_r, data->id, data->packet_id);
if (old_valid)
dml_packet_send_req_disc(dc, data->id);
}
continue;
}
connection_data_remove(data);
}
}
void update_clear(struct connection *con)
{
memset(con->update_id, 0, DML_ID_SIZE);
}
gboolean update(void *arg)
{
struct connection *con = arg;
// printf("update\n");
while (dml_connection_send_empty(con->dc)) {
struct connection_update *up;
up = con->bad_list;
if (up)
con->bad_list = up->next;
else {
up = con->good_list;
if (up)
con->good_list = up->next;
}
if (!up)
break;
char *idstr = dml_id_str(up->id);
printf("Send update %s (%d hops)\n", idstr, up->hops);
free(idstr);
dml_packet_send_route(con->dc, up->id, up->hops);
free(up);
}
if (!dml_connection_send_empty(con->dc)) {
if (con->bad_list || con->good_list)
printf("Send not empty, but update waiting\n");
}
// printf("wait a little %p\n", con);
g_timeout_add_seconds(1, update, con);
return G_SOURCE_REMOVE;
}
gboolean update_all(void *arg)
{
struct connection *con = arg;
dml_route_sort_lock_dec();
// printf("g\n");
while (dml_connection_send_empty(con->dc)) {
uint8_t hops;
struct dml_connection *dc;
int r;
r = dml_route_iterate(con->update_id, &hops, &dc);
// printf("r: %d\n", r);
if (r) {
// printf("switch to regular updates %p\n", con);
dml_packet_send_update(con->dc, DML_PACKET_UPDATE_INITIAL_DONE);
g_timeout_add_seconds(1, update, con);
return G_SOURCE_REMOVE;
}
/* no update to the originating node */
if (dc == con->dc)
continue;
hops = hops == 255 ? 255 : hops + 1;
dml_packet_send_route(con->dc, con->update_id, hops);
}
// printf("wait a little %p\n", con);
dml_route_sort_lock_inc();
g_timeout_add_seconds(1, update_all, con);
return G_SOURCE_REMOVE;
}
void connection_update(uint8_t id[DML_ID_SIZE], uint8_t hops, struct dml_connection *dc, bool bad, uint8_t alt_hops)
{
struct connection *con;
// printf("got update\n");
for (con = connection_list; con; con = con->next) {
struct connection_update *up, **upp;
uint8_t up_hops = con->dc == dc ? alt_hops : hops;
up_hops = up_hops == 255 ? 255 : up_hops + 1;
for (up = con->bad_list; up; up = up->next) {
if (!memcmp(up->id, id, DML_ID_SIZE)) {
up->hops = up_hops;
printf("Already on bad list\n");
break;
}
}
if (up)
continue;
for (upp = &con->good_list; *upp; upp = &(*upp)->next) {
if (!memcmp((*upp)->id, id, DML_ID_SIZE)) {
up = *upp;
*upp = up->next;
printf("Already on good list\n");
break;
}
}
if (!up) {
up = malloc(sizeof(struct connection_update));
memcpy(up->id, id, DML_ID_SIZE);
}
up->hops = up_hops;
if (bad) {
up->next = con->bad_list;
con->bad_list = up;
printf("On bad list\n");
/* It is bad, so we want updates a bit faster */
g_source_remove_by_user_data(con);
g_timeout_add(100, update, con);
} else {
up->next = con->good_list;
con->good_list = up;
printf("On good list\n");
}
}
struct connection_data *cdat = connection_data_by_id(id);
if (cdat) {
if (dc) {
if (cdat->dc != dc) {
dml_packet_send_req_disc(cdat->dc, cdat->id);
cdat->dc = dc;
dml_packet_send_connect(dc, cdat->id, cdat->packet_id);
}
} else {
connection_data_remove(cdat);
}
}
}
int list_add(struct connection_update **list, uint8_t id[DML_ID_SIZE])
{
struct connection_update **entry, *new_id;
for (entry = list; *entry; entry = &(*entry)->next) {
if (!memcmp(id, (*entry)->id, DML_ID_SIZE)) {
return 0;
}
}
new_id = malloc(sizeof(struct connection_update));
if (!new_id)
return -1;
memcpy(new_id->id, id, DML_ID_SIZE);
new_id->next = *list;
*list = new_id;
return 0;
}
bool list_check_remove(struct connection_update **list, uint8_t id[DML_ID_SIZE])
{
struct connection_update **entry;
for (entry = list; *entry; entry = &(*entry)->next) {
if (!memcmp(id, (*entry)->id, DML_ID_SIZE)) {
struct connection_update *remove = *entry;
*entry = (*entry)->next;
free(remove);
return true;
}
}
return false;
}
void rx_packet(struct dml_connection *dc, void *arg,
uint16_t id, uint16_t len, uint8_t *data)
{
struct connection *con = arg;
if (debug) printf("packet: %d\n", id);
switch (id) {
case DML_PACKET_HELLO:
dml_packet_parse_hello(data, len, &con->flags, NULL);
if (con->flags & DML_PACKET_HELLO_UPDATES) {
update_clear(con);
dml_route_sort_lock_inc();
update_all(con);
}
break;
case DML_PACKET_ROUTE: {
uint8_t id[DML_ID_SIZE];
uint8_t hops;
dml_packet_parse_route(data, len, id, &hops);
dml_route_update(id, hops, dc);
break;
}
case DML_PACKET_REQ_DESCRIPTION: {
uint8_t id[DML_ID_SIZE];
struct dml_connection *dc_r;
dml_packet_parse_req_description(data, len, id);
dc_r = dml_route_connection_get(id);
if (dc_r) {
printf("Request description\n");
dml_packet_send_req_description(dc_r, id);
} else {
printf("Description requested but id is not routable\n");
}
list_add(&con->req_description, id);
break;
}
case DML_PACKET_DESCRIPTION: {
uint8_t desc_id[DML_ID_SIZE];
uint8_t version;
uint32_t bps;
char *mime, *name, *alias, *description;
if (dml_packet_parse_description(data, len,
desc_id, &version, &bps, &mime, &name, &alias, &description))
break;
printf("Got description for %s\n", name);
struct connection *con;
for (con = connection_list; con; con = con->next) {
if (list_check_remove(&con->req_description, desc_id)) {
printf("Send description\n");
dml_connection_send(con->dc, data, id, len);
}
}
free(description);
free(alias);
free(name);
free(mime);
break;
}
case DML_PACKET_REQ_CERTIFICATE: {
uint8_t id[DML_ID_SIZE];
struct dml_connection *dc_r;
dml_packet_parse_req_certificate(data, len, id);
dc_r = dml_route_connection_get(id);
if (dc_r) {
dml_packet_send_req_certificate(dc_r, id);
}
list_add(&con->req_certificate, id);
break;
}
case DML_PACKET_CERTIFICATE: {
uint8_t id[DML_ID_SIZE];
void *certificate_data;
size_t certificate_len;
if (dml_packet_parse_certificate(data, len, id,
&certificate_data, &certificate_len))
break;
struct connection *con;
for (con = connection_list; con; con = con->next) {
if (list_check_remove(&con->req_certificate, id)) {
dml_packet_send_certificate(con->dc, id,
certificate_data, certificate_len);
}
}
free(certificate_data);
break;
}
case DML_PACKET_REQ_HEADER: {
uint8_t id[DML_ID_SIZE];
struct dml_connection *dc_r;
dml_packet_parse_req_header(data, len, id);
dc_r = dml_route_connection_get(id);
char *idstr = dml_id_str(id);
printf("Request header for %s: %p\n", idstr, dc_r);
free(idstr);
if (dc_r) {
dml_packet_send_req_header(dc_r, id);
}
list_add(&con->req_header, id);
break;
}
case DML_PACKET_HEADER: {
uint8_t id[DML_ID_SIZE];
uint8_t sig[DML_SIG_SIZE];
void *header_data;
size_t header_len;
if (dml_packet_parse_header(data, len, id, sig,
&header_data, &header_len))
break;
char *idstr = dml_id_str(id);
printf("Got header for %s\n", idstr);
free(idstr);
struct connection *con;
for (con = connection_list; con; con = con->next) {
if (list_check_remove(&con->req_header, id)) {
dml_packet_send_header(con->dc, id,
sig, header_data, header_len);
}
}
free(header_data);
break;
}
case DML_PACKET_CONNECT: {
uint8_t id[DML_ID_SIZE];
uint16_t packet_id;
if (dml_packet_parse_connect(data, len, id, &packet_id))
break;
struct connection_data *cdat = connection_data_by_id(id);
if (!cdat) {
struct dml_connection *dc_r = dml_route_connection_get(id);
printf("No data for this id yet\n");
if (!dc_r) {
dml_packet_send_disc(dc, id, DML_PACKET_DISC_UNROUTABLE);
break;
}
cdat = connection_data_create();
cdat->packet_id = connection_data_new_id();
cdat->dc = dc_r;
memcpy(cdat->id, id, DML_ID_SIZE);
printf("Sending connect\n");
dml_packet_send_connect(dc_r, id, cdat->packet_id);
}
connection_data_add_client(cdat, dc, packet_id);
break;
}
case DML_PACKET_DISC: {
uint8_t id[DML_ID_SIZE];
uint8_t reason;
if (dml_packet_parse_disc(data, len, id, &reason))
break;
struct connection_data *cdat = connection_data_by_id(id);
if (!cdat || cdat->dc != dc)
break;
if (reason & DML_PACKET_DISC_UNROUTABLE) {
struct dml_connection *dc_r = dml_route_connection_get(id);
if (dc_r && dc_r != dc) {
cdat->dc = dc_r;
dml_packet_send_connect(dc_r, id, cdat->packet_id);
break;
}
}
connection_data_remove(cdat);
break;
}
case DML_PACKET_REQ_DISC: {
uint8_t id[DML_ID_SIZE];
if (dml_packet_parse_req_disc(data, len, id))
break;
struct connection_data *cdat = connection_data_by_id(id);
if (!cdat)
break;
connection_data_remove_client(cdat, dc);
dml_packet_send_disc(dc, id, DML_PACKET_DISC_REQUESTED);
if (!cdat->client_list) {
printf("Sending disconnect request upstream\n");
dml_packet_send_req_disc(cdat->dc, cdat->id);
connection_data_remove(cdat);
}
break;
}
case DML_PACKET_REQ_REVERSE: {
uint8_t id[DML_ID_SIZE];
uint8_t rev_id[DML_ID_SIZE];
uint8_t action;
uint16_t status;
if (dml_packet_parse_req_reverse(data, len, id, rev_id, &action, &status))
break;
struct dml_connection *dc_r = dml_route_connection_get(id);
if (!dc_r)
break;
printf("Sending req_reverse: action=%d, status=%d\n", action, status);
dml_packet_send_req_reverse(dc_r, id, rev_id, action, status);
break;
}
default: {
/* Is it an unknown id or data? */
if (id < DML_PACKET_DATA)
break;
if (debug) printf("Got data (%d)\n", len);
struct connection_data *cdat = connection_data_by_connection(dc, id);
if (!cdat)
break;
if (debug) printf("Found connection\n");
cdat->t_data = time(NULL);
struct connection_data_client *cdatc;
for (cdatc = cdat->client_list; cdatc; cdatc = cdatc->next) {
if (debug) printf("Sending to client as %d\n", cdatc->packet_id);
dml_connection_send_data(cdatc->dc, data, cdatc->packet_id, len);
}
break;
}
}
}
int server_connection_close(struct dml_connection *dc, void *arg)
{
struct connection *con = arg;
printf("server close %p %s\n", dc, connection_name_get(con));
dml_route_remove(dc);
connection_destroy(con);
return dml_connection_destroy(dc);
}
void server_connection(void *arg, int fd)
{
struct dml_connection *dc;
struct connection *con;
char *name;
if (asprintf(&name, "server-%d", fd) < 0)
return;
con = connection_create();
if (!con)
return;
connection_name_set(con, name);
free(name);
dc = dml_connection_create(fd, con, rx_packet, server_connection_close);
// printf("new server connection %p %p\n", con, dc);
con->dc = dc;
dml_packet_send_hello(dc, DML_PACKET_HELLO_UPDATES, "dmld " DML_VERSION);
}
gboolean client_reconnect(void *clientv)
{
struct dml_client *client = clientv;
if (dml_client_connect(client)) {
printf("Reconnect to DML server failed\n");
g_timeout_add_seconds(2, client_reconnect, client);
}
return G_SOURCE_REMOVE;
}
int client_connection_close(struct dml_connection *dc, void *arg)
{
struct connection *con = arg;
printf("client close %p %s\n", dc, connection_name_get(con));
struct dml_client *client = con->client;
g_timeout_add_seconds(1, client_reconnect, client);
dml_route_remove(dc);
connection_destroy(con);
return dml_connection_destroy(dc);
}
void client_connect(struct dml_client *client, void *arg)
{
struct dml_connection *dc;
struct connection *con;
char *name = arg;
printf("Connected to DML server %s\n", name);
con = connection_create();
if (!con)
return;
connection_name_set(con, name);
int fd = dml_client_fd_get(client);
dc = dml_connection_create(fd, con, rx_packet, client_connection_close);
con->dc = dc;
con->client = client;
dml_packet_send_hello(dc, DML_PACKET_HELLO_UPDATES, "dmld " DML_VERSION);
}
gboolean cleanup(void *arg)
{
int r;
static uint8_t id[DML_ID_SIZE] = { 0 };
uint8_t hops;
struct dml_connection *dc;
r = dml_route_iterate(id, &hops, &dc);
if (r) {
memset(id, 0, sizeof(id));
} else {
if (hops == 255) {
printf("Removing route\n");
dml_route_destroy(id);
}
}
if (dml_route_sort()) {
printf("Sorted routes\n");
}
g_timeout_add_seconds(1, cleanup, cleanup);
return G_SOURCE_REMOVE;
}
int main(int argc, char **argv)
{
struct dml_server *ds;
char *file = "dmld.conf";
char *server = NULL;
if (argc > 1)
file = argv[1];
if (dml_config_load(file)) {
printf("Failed to load config file %s\n", file);
return -1;
}
ds = dml_server_create(server_connection, NULL);
if (!ds) {
perror("Could not create server");
}
while ((server = dml_config_value("server", server, NULL))) {
struct dml_client *dc;
printf("Connect to %s\n", server);
dc = dml_client_create(server, 0, client_connect, strdup(server));
if (dml_client_connect(dc)) {
printf("Failed to connect to %s, try again later\n", server);
g_timeout_add_seconds(1, client_reconnect, dc);
}
}
dml_route_update_cb_set(connection_update);
g_timeout_add_seconds(1, cleanup, cleanup);
g_timeout_add(DMLD_DATA_KEEPALIVE, connection_data_keepalive, connection_data_keepalive);
g_main_loop_run(g_main_loop_new(NULL, false));
return 0;
}