#include "common.h" #include #include #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #include #include #include #endif #include "seafile-session.h" #include "utils.h" #include "net.h" #include "listen-mgr.h" #include "block-tx-server.h" #define DEBUG_FLAG SEAFILE_DEBUG_OTHER #include "log.h" #define DEFAULT_SERVER_PORT 12001 #define TOKEN_LEN 37 /* a uuid */ #define CHECK_EXPIRE_INTERVAL 1 #define READ_TOKEN_TIMEOUT 180 /* bufferevent read timeout */ struct _SeafListenManagerPriv { GHashTable *token_hash; struct evconnlistener *listener; CcnetTimer *check_timer; }; typedef struct { int ttl; ConnAcceptedCB func; void *user_data; } CallBackStruct; static void accept_connection (struct evconnlistener *listener, evutil_socket_t connfd, struct sockaddr *saddr, int socklen, void *vmanager); static int token_expire_pulse (void * vmanager); static void read_cb (struct bufferevent *bufev, void *user_data); static void error_cb (struct bufferevent *bufev, short what, void *user_data); static int get_listen_port (SeafileSession *session) { char *port_str; int port = 0; port_str = g_key_file_get_string (session->config, "network", "port", NULL); if (port_str) { port = atoi(port_str); if (port <= 0 || port > 65535) port = DEFAULT_SERVER_PORT; g_free(port_str); } return port; } SeafListenManager * seaf_listen_manager_new (SeafileSession *session) { SeafListenManager *mgr; mgr = g_new0 (SeafListenManager, 1); mgr->port = get_listen_port(session); mgr->priv = g_new0 (SeafListenManagerPriv, 1); mgr->priv->token_hash = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, g_free); return mgr; } int seaf_listen_manager_start (SeafListenManager *mgr) { evutil_socket_t listenfd; unsigned flags; SeafListenManagerPriv *priv = mgr->priv; if (mgr->port == 0) { return 0; } listenfd = ccnet_net_bind_tcp (mgr->port, 1); if (listenfd < 0) { seaf_warning ("[listen mgr] failed to bind port %d\n", mgr->port); return -1; } flags = LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_EXEC; /* start to listen on block transfer port */ priv->listener = evconnlistener_new (NULL, /* base */ accept_connection, mgr, /* cb & arg */ flags, /* flags */ -1, /* backlog */ listenfd); /* socket */ if (!priv->listener) { seaf_warning ("[listen mgr] failed to start evlistener\n"); evutil_closesocket (listenfd); return -1; } priv->check_timer = ccnet_timer_new (token_expire_pulse, mgr, CHECK_EXPIRE_INTERVAL * 1000); seaf_message ("listen on port %d for block tranfer\n", mgr->port); return 0; } static void accept_connection (struct evconnlistener *listener, evutil_socket_t connfd, struct sockaddr *saddr, int socklen, void *vmanager) { struct bufferevent *bufev; struct timeval tv; tv.tv_sec = READ_TOKEN_TIMEOUT; tv.tv_usec = 0; bufev = bufferevent_socket_new (NULL, connfd, 0); bufferevent_setcb (bufev, read_cb, NULL, error_cb, vmanager); bufferevent_setwatermark (bufev, EV_READ, TOKEN_LEN, TOKEN_LEN); bufferevent_set_timeouts (bufev, &tv, NULL); bufferevent_enable (bufev, EV_READ); /* no write is needed here*/ bufferevent_disable (bufev, EV_WRITE); } static void read_cb (struct bufferevent *bufev, void *user_data) { char *token; CallBackStruct *cbstruct; SeafListenManager *mgr = user_data; size_t len = EVBUFFER_LENGTH(bufev->input); evutil_socket_t connfd = bufferevent_getfd(bufev); /* we set the high & low watermark to TOKEN_LEN, so the received data can * only be this length. */ if (len != TOKEN_LEN) { seaf_warning ("[listen mgr] token with incorrect length received: %d\n", (int)len); goto error; } token = (char *)(EVBUFFER_DATA (bufev->input)); /* Switch to new block protocol */ if (strcmp (token, BLOCK_PROTOCOL_SIGNATURE) == 0) { if (ccnet_net_make_socket_blocking (connfd) < 0) { seaf_warning ("[listen mgr] Failed to set socket blocking.\n"); goto error; } if (block_tx_server_start (connfd) < 0) { seaf_warning ("Failed to start block tx server.\n"); goto error; } bufferevent_free (bufev); return; } cbstruct = g_hash_table_lookup (mgr->priv->token_hash, token); if (!cbstruct) { seaf_warning ("[listen mgr] unknown token received: %s\n", token); goto error; } /* The connfd should be non-blocking for adding to bufferevent. * But now we want it to be blocking again. */ if (ccnet_net_make_socket_blocking (connfd) < 0) { seaf_warning ("[listen mgr] Failed to set socket blocking.\n"); goto error; } /* client is now connected, execute the callback function */ cbstruct->func (connfd, cbstruct->user_data); g_hash_table_remove (mgr->priv->token_hash, token); bufferevent_free (bufev); return; error: evutil_closesocket(connfd); bufferevent_free (bufev); } static void error_cb (struct bufferevent *bufev, short what, void *user_data) { if (what & BEV_EVENT_TIMEOUT) seaf_warning ("[listen mgr] client timeout\n"); else seaf_warning ("[listen mgr] error when reading token\n"); /* We don't specify BEV_OPT_CLOSE_ON_FREE, so we need to close the socket * manually. */ evutil_closesocket(bufferevent_getfd(bufev)); bufferevent_free (bufev); } int seaf_listen_manager_register_token (SeafListenManager *mgr, const char *token, ConnAcceptedCB cb, void *cb_arg, int timeout_sec) { CallBackStruct *cbstruct; if (!token) return -1; if (timeout_sec <= 0) return -1; cbstruct = g_new0(CallBackStruct, 1); cbstruct->func = cb; cbstruct->user_data = cb_arg; cbstruct->ttl = timeout_sec; g_hash_table_insert (mgr->priv->token_hash, g_strdup(token), cbstruct); return 0; } char * seaf_listen_manager_generate_token (SeafListenManager *mgr) { return gen_uuid(); } static gboolean is_token_expired (gpointer key, gpointer value, gpointer user_data) { CallBackStruct *cbstruct = value; if (cbstruct->ttl == 0) { /* client doesn't connect before timeout, so token is expired */ seaf_warning ("[listen mgr] token timeout\n"); cbstruct->func (-1, cbstruct->user_data); return TRUE; } --cbstruct->ttl; return FALSE; } static int token_expire_pulse (void * vmanager) { SeafListenManager *mgr = vmanager; g_hash_table_foreach_remove (mgr->priv->token_hash, (GHRFunc)is_token_expired, NULL); return TRUE; }