+Thu Feb 15 20:07:37 EET 2001 Pekka Riikonen <priikone@poseidon.pspt.fi>
+
+ * Added new packet type SILC_PACKET_HEARTBEAT that is used to
+ send keepalive packets. The packet can be sent by clients,
+ servers and routers.
+
+ Added function silc_socket_set_heartbeat into the file
+ lib/silccore/silcsockconn.[ch] to set the heartbeat timeout.
+ If not set, the heartbeat is not performed. The actual
+ heartbeat is implemented in the low level socket connection
+ library. However, application is responsible of actually
+ sending the packet.
+
+ Added silc_server_send_heartbeat to send the actual heartbeat
+ packet into silcd/packet_send.[ch]. Server now performs
+ keepalive with all connections.
+
+ * Added silc_task_get_first function into lib/silcutil/silctask.c
+ to return the timeout task with shortest timeout. There was a bug
+ in task unregistration that caused problems. TODO has been
+ updated to include that task system must be rewritten.
+
+ * The client library will now resolve the client information when
+ receiving JOIN notify from server for client that we know but
+ have incomplete information.
+
+ * Rewrote parts of silc_server_remove_from_channels and
+ silc_server_remove_from_one_channel as they did not remove the
+ channel in some circumstances even though they should've.
+
+ * Encryption problem encountered in server:
+
+ The LEAVE command used to send the Channel Key packet to the
+ router immediately after generating it. However, the code
+ had earlier sent Remove Channel user packet but not immediately,
+ ie. it was put to queue. The order of packets in the router
+ was that Channel Key packet was first and Remove Channel User
+ packet was second, even though they were encrypted in the
+ reverse order. For this reason, MAC check failed. Now, this
+ is fixed by not sending the Channel Key packet immediately but
+ putting it to queue. However, this is more fundamental problem:
+ packets that are in queue should actually not be encrypted
+ because packets that are sent immediately gets encrypted
+ actually with wrong IV (and thus MAC check fails). So, packets
+ that are in queue should be encrypted when they are sent to
+ the wire and not when they put to the queue.
+
+ However, the problem is that the current system has not been
+ designed to work that way. Instead, the packet is encrypted
+ as soon as possible and left to the queue. The queue is then
+ just purged into wire. There won't be any fixes for this
+ any time soon. So, the current semantic for packet sending
+ is as follows:
+
+ o If you send packet to remote host and do not force the send
+ (the packet will be in queue) then all subsequent packets to the
+ same remote host must also be put to the queue. Only after the
+ queue has been purged is it safe again to force the packet
+ send immediately.
+
+ o If you send all packets immediately then it safe to send
+ any of subsequent packets through the queue, however, after
+ the first packet is put to queue then any subsequent packets
+ must also be put to the queue.
+
+ Follow these rules and everything works fine.
+
Thu Feb 15 14:24:32 EET 2001 Pekka Riikonen <priikone@poseidon.pspt.fi>
* Added new function silc_server_remove_clients_by_server to
TODO In SILC Libraries
======================
+ o Rewrite the task system. I made it too complex and too "neat" and
+ it really should be rewritten. We don't need priorities really, one
+ priority is enough. This will simplify a lot the task system.
+
o Implement PFS (Perfect Forward Secrecy) flag in SKE (and in client and
server, actually). If PFS is set, re-key must cause new key exchange.
This is required by the SILC protocol.
char *sender, char *channel_name, char *msg)
{
/* Message from client */
- if (!strcmp(conn->current_channel->channel_name, channel_name))
+ if (conn && !strcmp(conn->current_channel->channel_name, channel_name))
silc_print(client, "<%s> %s", sender, msg);
else
silc_print(client, "<%s:%s> %s", sender, channel_name, msg);
/*
* $Id$
* $Log$
+ * Revision 1.4 2001/02/16 00:33:23 priikone
+ * updates.
+ *
* Revision 1.3 2001/02/14 15:31:33 priikone
* Do not allow several server connections.
*
port = 706;
}
+#if 0
if (conn && conn->remote_host) {
if (!strcmp(hostname, conn->remote_host) && port == conn->remote_port) {
silc_say(client, conn, "You are already connected to that server");
cmd->client->ops->disconnect(cmd->client, cmd->conn);
silc_client_close_connection(cmd->client, cmd->conn->sock);
}
+#endif
/* Connect asynchronously to not to block user interface */
silc_client_connect_to_server(cmd->client, port, hostname, NULL);
silc_server_packet_send(server,
cmd->server->router->connection,
SILC_PACKET_CHANNEL_KEY, 0, packet->data,
- packet->len, TRUE);
+ packet->len, FALSE);
} else {
}
/* Free channel entry. This free's everything. */
-void silc_idlist_del_channel(SilcIDList id_list, SilcChannelEntry entry)
+int silc_idlist_del_channel(SilcIDList id_list, SilcChannelEntry entry)
{
if (entry) {
SilcChannelClientEntry chl;
/* Remove from cache */
if (entry->id)
- silc_idcache_del_by_id(id_list->channels, SILC_ID_CHANNEL,
- (void *)entry->id);
+ if (!silc_idcache_del_by_id(id_list->channels, SILC_ID_CHANNEL,
+ (void *)entry->id))
+ return FALSE;
/* Free data */
if (entry->channel_name)
silc_free(chl);
}
}
+
+ return TRUE;
}
/* Finds channel by channel name. Channel names are unique and they
silc_idlist_add_channel(SilcIDList id_list, char *channel_name, int mode,
SilcChannelID *id, SilcServerEntry router,
SilcCipher channel_key);
-void silc_idlist_del_channel(SilcIDList id_list, SilcChannelEntry entry);
+int silc_idlist_del_channel(SilcIDList id_list, SilcChannelEntry entry);
SilcChannelEntry
silc_idlist_find_channel_by_name(SilcIDList id_list, char *name,
SilcIDCacheEntry *ret_entry);
silc_buffer_free(packet);
}
+
+/* Send the heartbeat packet. */
+
+void silc_server_send_heartbeat(SilcServer server,
+ SilcSocketConnection sock)
+{
+ silc_server_packet_send(server, sock, SILC_PACKET_HEARTBEAT, 0,
+ NULL, 0, FALSE);
+}
int broadcast,
int mode_type, unsigned int mode_mask,
unsigned int argc, ...);
+void silc_server_send_heartbeat(SilcServer server,
+ SilcSocketConnection sock);
#endif
silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, NULL, &newsocket);
server->sockets[sock] = newsocket;
newsocket->hostname = strdup(sconn->remote_host);
+ newsocket->ip = strdup(sconn->remote_host);
newsocket->port = sconn->remote_port;
sconn->sock = newsocket;
SilcSocketConnection sock = ctx->sock;
SilcServerEntry id_entry;
SilcBuffer packet;
+ SilcServerHBContext hb_context;
unsigned char *id_string;
SILC_LOG_DEBUG(("Start"));
server->router = id_entry;
server->router->data.registered = TRUE;
+ /* Perform keepalive. The `hb_context' will be freed automatically
+ when finally calling the silc_socket_free function. XXX hardcoded
+ timeout!! */
+ hb_context = silc_calloc(1, sizeof(*hb_context));
+ hb_context->server = server;
+ silc_socket_set_heartbeat(sock, 600, hb_context,
+ silc_server_perform_heartbeat,
+ server->timeout_queue);
+
out:
/* Free the temporary connection data context */
if (sconn)
(SilcServerConnAuthInternalContext *)protocol->context;
SilcServer server = (SilcServer)ctx->server;
SilcSocketConnection sock = ctx->sock;
+ SilcServerHBContext hb_context;
void *id_entry = NULL;
SILC_LOG_DEBUG(("Start"));
/* Connection has been fully established now. Everything is ok. */
SILC_LOG_DEBUG(("New connection authenticated"));
+ /* Perform keepalive. The `hb_context' will be freed automatically
+ when finally calling the silc_socket_free function. XXX hardcoded
+ timeout!! */
+ hb_context = silc_calloc(1, sizeof(*hb_context));
+ hb_context->server = server;
+ silc_socket_set_heartbeat(sock, 600, hb_context,
+ silc_server_perform_heartbeat,
+ server->timeout_queue);
+
silc_protocol_free(protocol);
if (ctx->packet)
silc_packet_context_free(ctx->packet);
silc_server_set_mode(server, sock, packet);
break;
+ case SILC_PACKET_HEARTBEAT:
+ /*
+ * Received heartbeat.
+ */
+ SILC_LOG_DEBUG(("Heartbeat packet"));
+ break;
+
default:
SILC_LOG_ERROR(("Incorrect packet type %d, packet dropped", type));
break;
{
SilcChannelEntry channel;
SilcChannelClientEntry chl;
- SilcBuffer chidp, clidp;
+ SilcBuffer clidp;
SILC_LOG_DEBUG(("Start"));
silc_list_start(client->channels);
while ((chl = silc_list_get(client->channels)) != SILC_LIST_END) {
channel = chl->channel;
- chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
- /* Remove from list */
+ /* Remove channel from client's channel list */
silc_list_del(client->channels, chl);
- /* If this client is last one on the channel the channel
- is removed all together. */
- if (silc_list_count(channel->user_list) < 2) {
+ /* Remove channel if there is no users anymore */
+ if (server->server_type == SILC_ROUTER &&
+ silc_list_count(channel->user_list) < 2) {
+ if (!silc_idlist_del_channel(server->local_list, channel))
+ silc_idlist_del_channel(server->global_list, channel);
+ server->stat.my_channels--;
+ continue;
+ }
- /* However, if the channel has marked global users then the
- channel is not created locally, and this does not remove the
- channel globally from SILC network, in this case we will
- notify that this client has left the channel. */
+ /* Remove client from channel's client list */
+ silc_list_del(channel->user_list, chl);
+ silc_free(chl);
+ server->stat.my_chanclients--;
+
+ /* If there is not at least one local user on the channel then we don't
+ need the channel entry anymore, we can remove it safely. */
+ if (server->server_type == SILC_SERVER &&
+ !silc_server_channel_has_local(channel)) {
+ /* Notify about leaving client if this channel has global users. */
if (channel->global_users)
silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
SILC_NOTIFY_TYPE_SIGNOFF, 1,
clidp->data, clidp->len);
-
- silc_idlist_del_channel(server->local_list, channel);
+
+ if (!silc_idlist_del_channel(server->local_list, channel))
+ silc_idlist_del_channel(server->global_list, channel);
server->stat.my_channels--;
continue;
}
- /* Remove from list */
- silc_list_del(channel->user_list, chl);
- silc_free(chl);
- server->stat.my_chanclients--;
-
/* Send notify to channel about client leaving SILC and thus
the entire channel. */
silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
SILC_NOTIFY_TYPE_SIGNOFF, 1,
clidp->data, clidp->len);
- silc_buffer_free(chidp);
}
silc_buffer_free(clidp);
ch = chl->channel;
- /* Remove from list */
+ /* Remove channel from client's channel list */
silc_list_del(client->channels, chl);
- /* If this client is last one on the channel the channel
- is removed all together. */
- if (silc_list_count(channel->user_list) < 2) {
- /* Notify about leaving client if this channel has global users. */
- if (notify && channel->global_users)
- silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
- SILC_NOTIFY_TYPE_LEAVE, 1,
- clidp->data, clidp->len);
-
- silc_idlist_del_channel(server->local_list, channel);
+ /* Remove channel if there is no users anymore */
+ if (server->server_type == SILC_ROUTER &&
+ silc_list_count(channel->user_list) < 2) {
+ if (!silc_idlist_del_channel(server->local_list, channel))
+ silc_idlist_del_channel(server->global_list, channel);
silc_buffer_free(clidp);
server->stat.my_channels--;
return FALSE;
}
- /* Remove from list */
+ /* Remove client from channel's client list */
silc_list_del(channel->user_list, chl);
silc_free(chl);
server->stat.my_chanclients--;
!silc_server_channel_has_global(channel))
channel->global_users = FALSE;
- /* If tehre is not at least one local user on the channel then we don't
+ /* If there is not at least one local user on the channel then we don't
need the channel entry anymore, we can remove it safely. */
if (server->server_type == SILC_SERVER &&
!silc_server_channel_has_local(channel)) {
- silc_idlist_del_channel(server->local_list, channel);
+ /* Notify about leaving client if this channel has global users. */
+ if (notify && channel->global_users)
+ silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
+ SILC_NOTIFY_TYPE_LEAVE, 1,
+ clidp->data, clidp->len);
+
+ if (!silc_idlist_del_channel(server->local_list, channel))
+ silc_idlist_del_channel(server->global_list, channel);
silc_buffer_free(clidp);
server->stat.my_channels--;
return FALSE;
return channel;
}
+
+/* Heartbeat callback. This function is set as argument for the
+ silc_socket_set_heartbeat function. The library will call this function
+ at the set time interval. */
+
+void silc_server_perform_heartbeat(SilcSocketConnection sock,
+ void *hb_context)
+{
+ SilcServerHBContext hb = (SilcServerHBContext)hb_context;
+
+ SILC_LOG_DEBUG(("Sending heartbeat to %s (%s)", sock->hostname,
+ sock->ip));
+
+ /* Send the heartbeat */
+ silc_server_send_heartbeat(hb->server, sock);
+}
SilcChannelEntry silc_server_save_channel_key(SilcServer server,
SilcBuffer key_payload,
SilcChannelEntry channel);
+void silc_server_perform_heartbeat(SilcSocketConnection sock,
+ void *hb_context);
#endif
#endif
};
+/* Server's heartbeat context */
+typedef struct {
+ SilcServer server;
+} *SilcServerHBContext;
+
/* Macros */
/* Registers generic task for file descriptor for reading from network and
Mun huone:Mun servo:Pekka Riikonen:priikone@poseidon.pspt.fi
[ServerInfo]
-lassi.kuo.fi.ssh.com:10.2.1.7:Kuopio, Finland:1334
+lassi.kuo.fi.ssh.com:212.146.8.245:Kuopio, Finland:1334
[ListenPort]
-10.2.1.7:10.2.1.7:1334
+212.146.8.245:212.146.8.245:1334
[Logging]
infologfile:silcd2.log:10000
[AdminConnection]
[ServerConnection]
-10.2.1.7:passwd:priikone:1333:1:1
+212.146.8.245:passwd:priikone:1333:1:1
[RouterConnection]
-10.2.1.7:passwd:priikone:1335:1:1:0
+212.146.8.245:passwd:priikone:1335:1:1:0
[DenyConnection]
[RedirectClient]
Payload of the packet: See section 2.3.27 Set Mode Payload
- 32 - 199
+ 32 SILC_PACKET_HEARTBEAT
+
+ This packet is used by clients, servers and routers to keep the
+ connection alive. It is recommended that all servers implement
+ keepalive actions and perform it to both direction in a link.
+ This packet does not have a payload.
+
+
+ 33 - 199
Currently undefined commands.
break;
}
+ case SILC_PACKET_HEARTBEAT:
+ /*
+ * Received heartbeat packet
+ */
+ SILC_LOG_DEBUG(("Heartbeat packet"));
+ break;
+
default:
SILC_LOG_DEBUG(("Incorrect packet type %d, packet dropped", type));
break;
goto out;
}
+ /* If nickname or username hasn't been resolved, do so */
+ if (!client_entry->nickname || !client_entry->username) {
+ SilcPacketContext *p = silc_packet_context_dup(packet);
+ SilcBuffer idp = silc_id_payload_encode(client_id, SILC_ID_CLIENT);
+ silc_client_send_command(client, conn, SILC_COMMAND_WHOIS,
+ SILC_IDLIST_IDENT, 1,
+ 3, idp->data, idp->len);
+ p->context = (void *)client;
+ p->sock = sock;
+ silc_client_command_pending(conn, SILC_COMMAND_WHOIS, SILC_IDLIST_IDENT,
+ silc_client_notify_by_server_pending, p);
+ goto out;
+ }
+
/* Get channel entry */
channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
SILC_ID_CHANNEL);
"Ping reply from %s: %d second%s",
conn->ping[i].dest_name, diff,
diff == 1 ? "" : "s");
-
+
conn->ping[i].start_time = 0;
silc_free(conn->ping[i].dest_id);
conn->ping[i].dest_id = NULL;
silc_free(conn->ping[i].dest_name);
conn->ping[i].dest_name = NULL;
-
- /* Notify application */
- COMMAND_REPLY((ARGS));
break;
}
}
silc_free(id);
+ /* Notify application */
+ COMMAND_REPLY((ARGS));
+
/* Execute any pending command callbacks */
SILC_CLIENT_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_PING);
{
unsigned char mac[32];
- if (cipher) {
- SILC_LOG_DEBUG(("Encrypting packet, cipher %s, len %d (%d)",
- cipher->cipher->name, len, len - 2));
- }
-
/* Compute HMAC. This assumes that HMAC is created from the entire
data area thus this uses the length found in buffer, not the length
sent as argument. */
/* Encrypt the data area of the packet. 2 bytes of the packet
are not encrypted. */
- if (cipher)
+ if (cipher) {
+ SILC_LOG_DEBUG(("Encrypting packet, cipher %s, len %d (%d)",
+ cipher->cipher->name, len, len - 2));
cipher->cipher->encrypt(cipher->context, buffer->data + 2,
buffer->data + 2, len - 2, cipher->iv);
+ }
/* Pull the HMAC into the visible data area in the buffer */
if (hmac)
#define SILC_PACKET_REKEY 29
#define SILC_PACKET_REKEY_DONE 30
#define SILC_PACKET_SET_MODE 31 /* Set mode */
+#define SILC_PACKET_HEARTBEAT 32 /* Heartbeat */
/* #define SILC_PACKET_MAX 255 */
/* Macros */
GNU General Public License for more details.
*/
-/*
- * $Id$
- * $Log$
- * Revision 1.3 2001/02/11 14:09:34 priikone
- * Code auditing weekend results and fixes committing.
- *
- * Revision 1.2 2000/07/05 06:06:35 priikone
- * Global cosmetic change.
- *
- * Revision 1.1.1.1 2000/06/27 11:36:55 priikone
- * Imported from internal CVS/Added Log headers.
- *
- *
- */
+/* $Id$ */
#include "silcincludes.h"
if (sock) {
silc_buffer_free(sock->inbuf);
silc_buffer_free(sock->outbuf);
+ if (sock->hb) {
+ silc_task_unregister(sock->hb->timeout_queue, sock->hb->hb_task);
+ silc_free(sock->hb->hb_context);
+ silc_free(sock->hb);
+ }
+
+ memset(sock, 'F', sizeof(*sock));
silc_free(sock);
}
}
+
+/* Internal timeout callback to perform heartbeat */
+
+SILC_TASK_CALLBACK(silc_socket_heartbeat)
+{
+ SilcSocketConnectionHB hb = (SilcSocketConnectionHB)context;
+
+ if (!hb->heartbeat)
+ return;
+
+ if (hb->hb_callback)
+ hb->hb_callback(hb->sock, hb->hb_context);
+
+ hb->hb_task = silc_task_register(hb->timeout_queue, hb->sock->sock,
+ silc_socket_heartbeat,
+ context, hb->heartbeat, 0,
+ SILC_TASK_TIMEOUT,
+ SILC_TASK_PRI_LOW);
+}
+
+/* Sets the heartbeat timeout and prepares the socket for performing
+ heartbeat in `heartbeat' intervals (seconds). */
+
+void silc_socket_set_heartbeat(SilcSocketConnection sock,
+ unsigned long heartbeat,
+ void *hb_context,
+ SilcSocketConnectionHBCb hb_callback,
+ void *timeout_queue)
+{
+ SilcSocketConnectionHB hb = silc_calloc(1, sizeof(*hb));
+
+ hb->heartbeat = heartbeat;
+ hb->hb_context = hb_context;
+ hb->hb_callback = hb_callback;
+ hb->timeout_queue = timeout_queue;
+ hb->sock = sock;
+ hb->hb_task = silc_task_register(timeout_queue, sock->sock,
+ silc_socket_heartbeat,
+ (void *)hb, heartbeat, 0,
+ SILC_TASK_TIMEOUT,
+ SILC_TASK_PRI_LOW);
+}
#ifndef SILCSOCKCONN_H
#define SILCSOCKCONN_H
+/* Forward declarations */
+typedef struct SilcSocketConnectionStruct *SilcSocketConnection;
+typedef struct SilcSocketConnectionHB *SilcSocketConnectionHB;
+
/* Socket types. These identifies the socket connection. */
typedef enum {
SILC_SOCKET_TYPE_UNKNOWN = 0,
#define SILC_SF_DISCONNECTING 3
#define SILC_SF_DISCONNECTED 4
+/* Heartbeat callback function. This is the function in the application
+ that this library will call when it is time to send the keepalive
+ packet SILC_PACKET_HEARTBEAT. */
+typedef void (*SilcSocketConnectionHBCb)(SilcSocketConnection sock,
+ void *context);
+
/*
SILC Socket Connection object.
inbuf buffer and outgoing data after encryption is put to the outbuf
buffer.
+ SilcSocketConnectionHB hb
+
+ The heartbeat context. If NULL, heartbeat is not performed.
+
*/
-typedef struct {
+struct SilcSocketConnectionStruct {
int sock;
SilcSocketType type;
void *user_data;
SilcBuffer inbuf;
SilcBuffer outbuf;
-} SilcSocketConnectionObject;
-typedef SilcSocketConnectionObject *SilcSocketConnection;
+ SilcSocketConnectionHB hb;
+};
+
+/* Heartbeat context */
+struct SilcSocketConnectionHB {
+ unsigned long heartbeat;
+ SilcSocketConnectionHBCb hb_callback;
+ void *hb_context;
+ void *timeout_queue;
+ SilcTask hb_task;
+ SilcSocketConnection sock;
+};
/* Macros */
void silc_socket_alloc(int sock, SilcSocketType type, void *user_data,
SilcSocketConnection *new_socket);
void silc_socket_free(SilcSocketConnection sock);
+void silc_socket_set_heartbeat(SilcSocketConnection sock,
+ unsigned long heartbeat,
+ void *hb_context,
+ SilcSocketConnectionHBCb hb_callback,
+ void *timeout_queue);
#endif
return new;
}
+#if 0
+void dump_tasks(SilcTaskQueue queue)
+{
+ SilcTask first, prev;
+
+ if (!queue->task)
+ return;
+
+ first = queue->task;
+
+ fprintf(stderr, "\nqueue->task:\t%p\t%d\n", queue->task,
+ queue->task->timeout.tv_sec);
+
+ prev = first->prev;
+ while (1) {
+ if (first == prev)
+ break;
+
+ fprintf(stderr, "task :\t%p\t%d\n", prev, prev->timeout.tv_sec);
+
+ prev = prev->prev;
+ }
+ fprintf(stderr, "\n");
+}
+#endif
+
+/* Return the timeout task with smallest timeout. */
+
+static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
+{
+ SilcTask prev, task;
+
+ prev = first->prev;
+
+ if (first == prev)
+ return first;
+
+ task = first;
+ while (1) {
+ if (first == prev)
+ break;
+
+ if (silc_task_timeout_compare(&prev->timeout, &task->timeout))
+ task = prev;
+
+ prev = prev->prev;
+ }
+
+ return task;
+}
+
/* Adds a timeout task into the task queue. This function is used by
silc_task_register function. Returns a pointer to the registered
task. Timeout tasks are sorted by their timeout value in ascending
return NULL;
}
+#if 0
+ dump_tasks(queue);
+#endif
+
return new;
}
if (prev == old && next == old)
queue->task = NULL;
if (queue->task == old)
- queue->task = next;
+ queue->task = silc_task_get_first(queue, next);
+ /*queue->task = next;*/
+
+#if 0
+ dump_tasks(queue);
+#endif
silc_free(old);
return TRUE;
}
- old = old->next;
+ old = old->prev;
if (old == first)
return FALSE;
#define SILC_TASK_CALLBACK(func) \
static void func(void *qptr, int type, void *context, int fd)
-
/* Prototypes */
void silc_task_queue_alloc(SilcTaskQueue *new, int valid);
void silc_task_queue_free(SilcTaskQueue old);