diff --git a/ChangeLog b/ChangeLog index 0b40548..f4e48b3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,8 @@ - Add code coverage support - New API: - xmpp_stanza_get_child_by_path() + - xmpp_conn_set_sockopt_callback() + - xmpp_sockopt_cb_keepalive() 0.11.0 - SASL EXTERNAL support (XEP-0178) diff --git a/examples/basic.c b/examples/basic.c index 3e7f9e9..69f73a1 100644 --- a/examples/basic.c +++ b/examples/basic.c @@ -15,10 +15,6 @@ #include -/* hardcoded TCP keepalive timeout and interval */ -#define KA_TIMEOUT 60 -#define KA_INTERVAL 1 - /* define a handler for connection events */ static void conn_handler(xmpp_conn_t *conn, xmpp_conn_event_t status, @@ -63,7 +59,7 @@ int main(int argc, char **argv) xmpp_ctx_t *ctx; xmpp_conn_t *conn; xmpp_log_t *log; - char *jid = NULL, *password = NULL, *cert = NULL, *key = NULL, *host = NULL; + char *jid = NULL, *password = NULL, *host = NULL; long flags = 0; int i; unsigned long port = 0; @@ -118,9 +114,6 @@ int main(int argc, char **argv) xmpp_conn_set_flags(conn, flags); /* setup authentication information */ - if (cert && key) { - xmpp_conn_set_client_cert(conn, cert, key); - } if (jid) xmpp_conn_set_jid(conn, jid); if (password) diff --git a/examples/bot.c b/examples/bot.c index 944593f..a66c6a4 100644 --- a/examples/bot.c +++ b/examples/bot.c @@ -142,21 +142,66 @@ void conn_handler(xmpp_conn_t *conn, } } +static void usage(int exit_code) +{ + fprintf(stderr, + "Usage: bot [options] \n\n" + "Options:\n" + " --jid The JID to use to authenticate.\n" + " --pass The password of the JID.\n" + " --tcp-keepalive Configure TCP keepalive.\n" + " --disable-tls Disable TLS.\n" + " --mandatory-tls Deny plaintext connection.\n" + " --trust-tls Trust TLS certificate.\n" + " --legacy-ssl Use old style SSL.\n" + " --legacy-auth Allow legacy authentication.\n" + "Note: --disable-tls conflicts with --mandatory-tls or " + "--legacy-ssl\n"); + + exit(exit_code); +} + int main(int argc, char **argv) { xmpp_ctx_t *ctx; xmpp_conn_t *conn; xmpp_log_t *log; - char *jid, *pass; + char *jid = NULL, *password = NULL, *host = NULL; + long flags = 0; + int i, tcp_keepalive = 0; + unsigned long port = 0; /* take a jid and password on the command line */ - if (argc != 3) { - fprintf(stderr, "Usage: bot \n\n"); - return 1; + for (i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--help") == 0) + usage(0); + else if (strcmp(argv[i], "--disable-tls") == 0) + flags |= XMPP_CONN_FLAG_DISABLE_TLS; + else if (strcmp(argv[i], "--mandatory-tls") == 0) + flags |= XMPP_CONN_FLAG_MANDATORY_TLS; + else if (strcmp(argv[i], "--trust-tls") == 0) + flags |= XMPP_CONN_FLAG_TRUST_TLS; + else if (strcmp(argv[i], "--legacy-ssl") == 0) + flags |= XMPP_CONN_FLAG_LEGACY_SSL; + else if (strcmp(argv[i], "--legacy-auth") == 0) + flags |= XMPP_CONN_FLAG_LEGACY_AUTH; + else if ((strcmp(argv[i], "--jid") == 0) && (++i < argc)) + jid = argv[i]; + else if ((strcmp(argv[i], "--pass") == 0) && (++i < argc)) + password = argv[i]; + else if (strcmp(argv[i], "--tcp-keepalive") == 0) + tcp_keepalive = 1; + else + break; + } + if ((!jid) || (argc - i) > 2) { + usage(1); } - jid = argv[1]; - pass = argv[2]; + if (i < argc) + host = argv[i]; + if (i + 1 < argc) + port = strtoul(argv[i + 1], NULL, 0); /* init library */ xmpp_initialize(); @@ -169,23 +214,26 @@ int main(int argc, char **argv) /* create a connection */ conn = xmpp_conn_new(ctx); - /* - * also you can disable TLS support or force legacy SSL - * connection without STARTTLS - * - * see xmpp_conn_set_flags() or examples/basic.c - */ + /* configure connection properties (optional) */ + xmpp_conn_set_flags(conn, flags); /* setup authentication information */ - xmpp_conn_set_jid(conn, jid); - xmpp_conn_set_pass(conn, pass); + if (jid) + xmpp_conn_set_jid(conn, jid); + if (password) + xmpp_conn_set_pass(conn, password); + + /* enable TCP keepalive, using canned callback function */ + if (tcp_keepalive) + xmpp_conn_set_sockopt_callback(conn, xmpp_sockopt_cb_keepalive); /* initiate connection */ - xmpp_connect_client(conn, NULL, 0, conn_handler, ctx); + if (xmpp_connect_client(conn, host, port, conn_handler, ctx) == XMPP_EOK) { - /* enter the event loop - - our connect handler will trigger an exit */ - xmpp_run(ctx); + /* enter the event loop - + our connect handler will trigger an exit */ + xmpp_run(ctx); + } /* release our connection and context */ xmpp_conn_release(conn); diff --git a/examples/complex.c b/examples/complex.c index dd531c1..00305ee 100644 --- a/examples/complex.c +++ b/examples/complex.c @@ -13,11 +13,24 @@ #include #include +#ifdef _WIN32 +#include +#include +#include +#include /* tcp_keepalive */ +#else +#include +#include +#include +#endif + #include /* hardcoded TCP keepalive timeout and interval */ #define KA_TIMEOUT 60 -#define KA_INTERVAL 1 +#define KA_INTERVAL 30 +#define KA_COUNT 3 +#define USER_TIMEOUT 150 static void print_tlscert(const xmpp_tlscert_t *cert) { @@ -80,6 +93,86 @@ static int certfail_handler(const xmpp_tlscert_t *cert, return read_char[0] == 'y' || read_char[0] == 'Y'; } +static int sockopt_cb(xmpp_conn_t *conn, void *socket) +{ + int timeout = KA_TIMEOUT; + int interval = KA_INTERVAL; + int count = KA_COUNT; + unsigned int user_timeout = USER_TIMEOUT; + int ret; + int optval = (timeout && interval) ? 1 : 0; + + (void)conn; + +#ifdef _WIN32 + (void)count; + (void)user_timeout; + + SOCKET sock = *((SOCKET *)socket); + struct tcp_keepalive ka; + DWORD dw = 0; + + ka.onoff = optval; + ka.keepalivetime = timeout * 1000; + ka.keepaliveinterval = interval * 1000; + ret = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &ka, sizeof(ka), NULL, 0, &dw, + NULL, NULL); +#else + int sock = *((int *)socket); + + fprintf(stderr, "DEBUG: setting socket options\n"); + + ret = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)); + if (ret < 0) + return ret; + + if (optval) { +#ifdef TCP_KEEPIDLE + ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &timeout, + sizeof(timeout)); +#elif defined(TCP_KEEPALIVE) + /* QNX receives `struct timeval' as argument, but it seems OSX does int + */ + ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &timeout, + sizeof(timeout)); +#endif /* TCP_KEEPIDLE */ + if (ret < 0) + return ret; +#ifdef TCP_KEEPINTVL + ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval, + sizeof(interval)); + if (ret < 0) + return ret; +#endif /* TCP_KEEPINTVL */ + } + + if (count) { +#ifdef TCP_KEEPCNT + ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count)); + if (ret < 0) + return ret; +#endif /* TCP_KEEPCNT */ + } + + if (user_timeout) { +#ifdef TCP_USER_TIMEOUT + ret = setsockopt(sock, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout, + sizeof(user_timeout)); + if (ret < 0) + return ret; +#elif defined(TCP_RXT_CONNDROPTIME) + int rxt = user_timeout / 1000; + ret = setsockopt(sock, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &rxt, + sizeof(rxt)); + if (ret < 0) + return ret; +#endif /* TCP_USER_TIMEOUT */ + } +#endif /* _WIN32 */ + + return ret; +} + static void usage(int exit_code) { fprintf(stderr, @@ -184,7 +277,7 @@ int main(int argc, char **argv) xmpp_conn_set_flags(conn, flags); /* configure TCP keepalive (optional) */ if (tcp_keepalive) - xmpp_conn_set_keepalive(conn, KA_TIMEOUT, KA_INTERVAL); + xmpp_conn_set_sockopt_callback(conn, sockopt_cb); /* setup authentication information */ if (cert && key) { diff --git a/src/common.h b/src/common.h index e14fccc..1b60b1b 100644 --- a/src/common.h +++ b/src/common.h @@ -174,6 +174,7 @@ struct _xmpp_conn_t { sock_t sock; int ka_timeout; /* TCP keepalive timeout */ int ka_interval; /* TCP keepalive interval */ + int ka_count; /* TCP keepalive count */ tls_t *tls; int tls_support; @@ -233,6 +234,7 @@ struct _xmpp_conn_t { xmpp_handlist_t *timed_handlers; hash_t *id_handlers; xmpp_handlist_t *handlers; + xmpp_sockopt_callback sockopt_cb; }; void conn_disconnect(xmpp_conn_t *conn); diff --git a/src/conn.c b/src/conn.c index af22498..b9e1ffe 100644 --- a/src/conn.c +++ b/src/conn.c @@ -46,11 +46,37 @@ #ifndef CONNECT_TIMEOUT /** @def CONNECT_TIMEOUT * The time to wait (in milliseconds) for a connection attempt to succeed - * or error. The default is 5 seconds. + * or error. The default is 5 seconds. */ #define CONNECT_TIMEOUT 5000 /* 5 seconds */ #endif +#ifndef KEEPALIVE_TIMEOUT +/** @def KEEPALIVE_TIMEOUT + * The time (in seconds) the connection needs to remain idle before TCP starts + * sending keepalive probes, if the socket option SO_KEEPALIVE has been set on + * this socket. + * c.f. `TCP_KEEPIDLE` in `man 7 tcp` for linux, FreeBSD and some others or + * `TCP_KEEPALIVE` on MacOS. + */ +#define KEEPALIVE_TIMEOUT 60 +#endif +#ifndef KEEPALIVE_INTERVAL +/** @def KEEPALIVE_INTERVAL + * The time (in seconds) between individual keepalive probes. + * c.f. `TCP_KEEPINTVL` in `man 7 tcp` + */ +#define KEEPALIVE_INTERVAL 30 +#endif +#ifndef KEEPALIVE_COUNT +/** @def KEEPALIVE_COUNT + * The maximum number of keepalive probes TCP should send before dropping the + * connection. + * c.f. `TCP_KEEPCNT` in `man 7 tcp` + */ +#define KEEPALIVE_COUNT 3 +#endif + static int _disconnect_cleanup(xmpp_conn_t *conn, void *userdata); static char *_conn_build_stream_tag(xmpp_conn_t *conn, char **attributes, @@ -112,8 +138,9 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx) conn->type = XMPP_UNKNOWN; conn->state = XMPP_STATE_DISCONNECTED; conn->sock = -1; - conn->ka_timeout = 0; - conn->ka_interval = 0; + conn->ka_timeout = KEEPALIVE_TIMEOUT; + conn->ka_interval = KEEPALIVE_INTERVAL; + conn->ka_count = KEEPALIVE_COUNT; conn->tls = NULL; conn->timeout_stamp = 0; conn->error = 0; @@ -171,6 +198,7 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx) /* we own (and will free) the hash values */ conn->id_handlers = hash_new(conn->ctx, 32, NULL); conn->handlers = NULL; + conn->sockopt_cb = NULL; /* give the caller a reference to connection */ conn->ref = 1; @@ -216,33 +244,30 @@ xmpp_conn_t *xmpp_conn_clone(xmpp_conn_t *conn) return conn; } -/** Set TCP keepalive parameters - * Turn on TCP keepalive and set timeout and interval. Zero timeout - * disables TCP keepalives. The parameters are applied immediately for - * a non disconnected object. Also, they are applied when the connection - * object connects successfully. +/** Register sockopt callback + * Set function to be called when a new socket is created to allow setting + * socket options before connection is started. * - * @param conn a Strophe connection object - * @param timeout TCP keepalive timeout in seconds - * @param interval TCP keepalive interval in seconds + * If the connection is already connected, this callback will be called + * immediately. + * + * To set options that can only be applied to disconnected sockets, the + * callback must be registered before connecting. + * + * @param conn The Strophe connection object this callback is being registered + * for + * @param callback a xmpp_sockopt_callback callback function that will receive + * notifications of connection status * * @ingroup Connections */ -void xmpp_conn_set_keepalive(xmpp_conn_t *conn, int timeout, int interval) + +void xmpp_conn_set_sockopt_callback(xmpp_conn_t *conn, + xmpp_sockopt_callback callback) { - int ret = 0; - - conn->ka_timeout = timeout; - conn->ka_interval = interval; - + conn->sockopt_cb = callback; if (conn->state != XMPP_STATE_DISCONNECTED) - ret = sock_set_keepalive(conn->sock, timeout, interval); - - if (ret < 0) { - strophe_error(conn->ctx, "xmpp", - "Setting TCP keepalive (%d,%d) error: %d", timeout, - interval, sock_error()); - } + callback(conn, &conn->sock); } /** Release a Strophe connection object. @@ -1487,13 +1512,11 @@ static int _conn_connect(xmpp_conn_t *conn, if (!conn->domain) return XMPP_EMEM; - conn->sock = sock_connect(host, port); + conn->sock = sock_connect(conn, host, port); strophe_debug(conn->ctx, "xmpp", "sock_connect() to %s:%u returned %d", host, port, conn->sock); if (conn->sock == -1) return XMPP_EINT; - if (conn->ka_timeout || conn->ka_interval) - sock_set_keepalive(conn->sock, conn->ka_timeout, conn->ka_interval); /* setup handler */ conn->conn_handler = callback; diff --git a/src/deprecated.c b/src/deprecated.c index f3f2aa6..46c5c03 100644 --- a/src/deprecated.c +++ b/src/deprecated.c @@ -231,3 +231,26 @@ int xmpp_vsnprintf(char *str, size_t count, const char *fmt, va_list arg) { return strophe_vsnprintf(str, count, fmt, arg); } + +/** Set TCP keepalive parameters + * Turn on TCP keepalive and set timeout and interval. Zero timeout + * disables TCP keepalives. The parameters are applied immediately for + * a non disconnected object. Also, they are applied when the connection + * object connects successfully. + * + * @param conn a Strophe connection object + * @param timeout TCP keepalive timeout in seconds + * @param interval TCP keepalive interval in seconds + * + * @note this function is deprecated + * @see xmpp_conn_set_sockopt_callback() + * + * @ingroup Deprecated + */ +void xmpp_conn_set_keepalive(xmpp_conn_t *conn, int timeout, int interval) +{ + conn->ka_timeout = timeout; + conn->ka_interval = interval; + conn->ka_count = 0; + xmpp_conn_set_sockopt_callback(conn, xmpp_sockopt_cb_keepalive); +} diff --git a/src/sock.c b/src/sock.c index 6f01b08..92d2c0f 100644 --- a/src/sock.c +++ b/src/sock.c @@ -33,8 +33,7 @@ #include #endif -#include "sock.h" -#include "snprintf.h" +#include "common.h" void sock_initialize(void) { @@ -69,7 +68,7 @@ static int _in_progress(int error) #endif } -sock_t sock_connect(const char *host, unsigned short port) +sock_t sock_connect(xmpp_conn_t *conn, const char *host, unsigned short port) { sock_t sock; char service[6]; @@ -95,6 +94,14 @@ sock_t sock_connect(const char *host, unsigned short port) if (sock < 0) continue; + if (conn->sockopt_cb != NULL) + err = (conn->sockopt_cb)(conn, &sock); + + if (err != 0) { + sock_close(sock); + continue; + } + err = sock_set_nonblocking(sock); if (err == 0) { err = connect(sock, ainfo->ai_addr, ainfo->ai_addrlen); @@ -109,12 +116,17 @@ sock_t sock_connect(const char *host, unsigned short port) return sock; } -int sock_set_keepalive(sock_t sock, int timeout, int interval) +int sock_set_keepalive(sock_t sock, + int timeout, + int interval, + int count, + unsigned int user_timeout) { int ret; int optval = (timeout && interval) ? 1 : 0; - /* This function doesn't change maximum number of keepalive probes */ + UNUSED(count); + UNUSED(user_timeout); #ifdef _WIN32 struct tcp_keepalive ka; @@ -149,11 +161,57 @@ int sock_set_keepalive(sock_t sock, int timeout, int interval) return ret; #endif /* TCP_KEEPINTVL */ } + + if (count) { +#ifdef TCP_KEEPCNT + ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count)); + if (ret < 0) + return ret; +#endif /* TCP_KEEPCNT */ + } + + if (user_timeout) { +#ifdef TCP_USER_TIMEOUT + ret = setsockopt(sock, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout, + sizeof(user_timeout)); + if (ret < 0) + return ret; +#elif defined(TCP_RXT_CONNDROPTIME) + int rxt = user_timeout / 1000; + ret = setsockopt(sock, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &rxt, + sizeof(rxt)); + if (ret < 0) + return ret; +#endif /* TCP_USER_TIMEOUT */ + } + #endif /* _WIN32 */ return ret; } +/** Example sockopt callback function + * An example function that can be used to set reasonable default keepalive + * options on sockets when registered for a connection with + * xmpp_conn_set_sockopt_callback() + * + * @param conn a Strophe connection object + * @param socket pointer to a socket descriptor + * + * @see xmpp_sockopt_callback for details on the `socket` parameter + * @ingroup Connections + */ +int xmpp_sockopt_cb_keepalive(xmpp_conn_t *conn, void *socket) +{ + sock_t sock = *((sock_t *)socket); + + return sock_set_keepalive( + sock, conn->ka_timeout, conn->ka_interval, conn->ka_count, + conn->ka_count + ? (conn->ka_timeout + conn->ka_interval * conn->ka_count) * 1000 + : 0); +} + int sock_close(sock_t sock) { #ifdef _WIN32 diff --git a/src/sock.h b/src/sock.h index ecd4686..6674cb0 100644 --- a/src/sock.h +++ b/src/sock.h @@ -22,6 +22,9 @@ typedef int sock_t; #else #include +#include +#include +#include /* tcp_keepalive */ typedef SOCKET sock_t; #endif @@ -30,7 +33,7 @@ void sock_shutdown(void); int sock_error(void); -sock_t sock_connect(const char *host, unsigned short port); +sock_t sock_connect(xmpp_conn_t *conn, const char *host, unsigned short port); int sock_close(sock_t sock); int sock_set_blocking(sock_t sock); @@ -40,6 +43,10 @@ int sock_write(sock_t sock, const void *buff, size_t len); int sock_is_recoverable(int error); /* checks for an error after connect, return 0 if connect successful */ int sock_connect_error(sock_t sock); -int sock_set_keepalive(sock_t sock, int timeout, int interval); +int sock_set_keepalive(sock_t sock, + int timeout, + int interval, + int count, + unsigned int user_timeout); #endif /* __LIBSTROPHE_SOCK_H__ */ diff --git a/strophe.h b/strophe.h index 49633f2..7a5fe1c 100644 --- a/strophe.h +++ b/strophe.h @@ -269,6 +269,41 @@ typedef void (*xmpp_conn_handler)(xmpp_conn_t *conn, typedef int (*xmpp_certfail_handler)(const xmpp_tlscert_t *cert, const char *const errormsg); +/** The function which will be called when Strophe creates a new socket. + * + * The `sock` argument is a pointer that is dependent on the architecture + * Strophe is compiled for. + * + * For POSIX compatible systems usage shall be: + * ``` + * int soc = *((int*)sock); + * ``` + * + * On Windows usage shall be: + * ``` + * SOCKET soc = *((SOCKET*)sock); + * ``` + * + * This function will be called for each socket that is created. + * + * `examples/bot.c` uses a libstrophe supplied callback function that sets + * basic keepalive parameters (`xmpp_sockopt_cb_keepalive()`). + * + * `examples/complex.c` implements a custom function that could be useful + * for an application. + * + * @param conn The Strophe connection object this callback originates from. + * @param sock A pointer to the underlying file descriptor. + * + * @return 0 on success, -1 on error + * + * @ingroup Connections + */ +typedef int (*xmpp_sockopt_callback)(xmpp_conn_t *conn, void *sock); + +/* an example callback that sets basic keepalive parameters */ +int xmpp_sockopt_cb_keepalive(xmpp_conn_t *conn, void *sock); + void xmpp_send_error(xmpp_conn_t *conn, xmpp_error_type_t type, char *text); xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx); xmpp_conn_t *xmpp_conn_clone(xmpp_conn_t *conn); @@ -294,7 +329,8 @@ void xmpp_conn_set_pass(xmpp_conn_t *conn, const char *pass); xmpp_ctx_t *xmpp_conn_get_context(xmpp_conn_t *conn); void xmpp_conn_disable_tls(xmpp_conn_t *conn); int xmpp_conn_is_secured(xmpp_conn_t *conn); -void xmpp_conn_set_keepalive(xmpp_conn_t *conn, int timeout, int interval); +void xmpp_conn_set_sockopt_callback(xmpp_conn_t *conn, + xmpp_sockopt_callback callback); int xmpp_conn_is_connecting(xmpp_conn_t *conn); int xmpp_conn_is_connected(xmpp_conn_t *conn); int xmpp_conn_is_disconnected(xmpp_conn_t *conn); @@ -592,9 +628,13 @@ void xmpp_rand_nonce(xmpp_rand_t *rand, char *output, size_t len); * deprecation */ #include -#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 405) +#if defined(__GNUC__) +#if (__GNUC__ * 100 + __GNUC_MINOR__ >= 405) #define XMPP_DEPRECATED \ __attribute__((deprecated("Function is internal from next release on"))) +#elif (__GNUC__ * 100 + __GNUC_MINOR__ >= 300) +#define XMPP_DEPRECATED __attribute__((deprecated)) +#endif #elif defined(_MSC_VER) && _MSC_VER >= 1500 #define XMPP_DEPRECATED \ __declspec(deprecated("Function is internal from next release on")) @@ -630,6 +670,9 @@ xmpp_debug(const xmpp_ctx_t *ctx, const char *area, const char *fmt, ...); XMPP_DEPRECATED void xmpp_debug_verbose( int level, const xmpp_ctx_t *ctx, const char *area, const char *fmt, ...); +XMPP_DEPRECATED void +xmpp_conn_set_keepalive(xmpp_conn_t *conn, int timeout, int interval); + #ifdef __cplusplus } #endif