+ conn->sock = sock;
+
+ /** Wait for connection */
+ silc_fsm_next(fsm, silc_net_connect_st_connected);
+ silc_fsm_sema_init(&conn->sema, fsm, 0);
+ silc_schedule_task_add_fd(silc_fsm_get_schedule(fsm), sock,
+ silc_net_connect_wait, conn);
+ silc_schedule_set_listen_fd(silc_fsm_get_schedule(fsm), sock,
+ SILC_TASK_WRITE, FALSE);
+ SILC_FSM_SEMA_WAIT(&conn->sema);
+ return SILC_FSM_CONTINUE;
+}
+
+static void silc_net_connect_wait_stream(SilcSocketStreamStatus status,
+ SilcStream stream, void *context)
+{
+ SilcNetConnect conn = context;
+ conn->stream_status = status;
+ conn->stream = stream;
+ SILC_FSM_CALL_CONTINUE(&conn->fsm);
+}
+
+SILC_FSM_STATE(silc_net_connect_st_connected)
+{
+ SilcNetConnect conn = fsm_context;
+ SilcSchedule schedule = silc_fsm_get_schedule(fsm);
+ int opt = EINVAL, optlen = sizeof(opt), ret;
+
+ if (conn->aborted) {
+ /** Aborted */
+ silc_fsm_next(fsm, silc_net_connect_st_finish);
+ return SILC_FSM_CONTINUE;
+ }
+
+ ret = silc_net_get_socket_opt(conn->sock, SOL_SOCKET, SO_ERROR,
+ &opt, &optlen);
+
+ silc_schedule_task_del_by_fd(schedule, conn->sock);
+ silc_schedule_unset_listen_fd(schedule, conn->sock);
+
+ if (ret != 0 || opt != 0) {
+ if (conn->retry) {
+ /** Retry connecting */
+ SILC_LOG_DEBUG(("Retry connecting"));
+ conn->retry--;
+ silc_net_close_connection(conn->sock);
+ silc_fsm_next(fsm, silc_net_connect_st_start);
+ return SILC_FSM_CONTINUE;
+ }
+
+#if defined(ECONNREFUSED)
+ if (errno == ECONNREFUSED)
+ conn->status = SILC_NET_CONNECTION_REFUSED;
+#endif /* ECONNREFUSED */
+#if defined(ETIMEDOUT)
+ if (errno == ETIMEDOUT)
+ conn->status = SILC_NET_CONNECTION_TIMEOUT;
+#endif /* ETIMEDOUT */
+#if defined(ENETUNREACH)
+ if (errno == ENETUNREACH)
+ conn->status = SILC_NET_HOST_UNREACHABLE;
+#endif /* ENETUNREACH */
+
+ /** Connecting failed */
+ SILC_LOG_DEBUG(("Connecting failed"));
+ silc_fsm_next(fsm, silc_net_connect_st_finish);
+ return SILC_FSM_CONTINUE;
+ }
+
+ /** Connection created */
+ silc_fsm_next(fsm, silc_net_connect_st_stream);
+ SILC_FSM_CALL((conn->sop = silc_socket_stream_create(
+ conn->sock, FALSE, FALSE,
+ schedule,
+ silc_net_connect_wait_stream, conn)));
+}
+
+SILC_FSM_STATE(silc_net_connect_st_stream)
+{
+ SilcNetConnect conn = fsm_context;
+
+ if (conn->aborted) {
+ /** Aborted */
+ silc_fsm_next(fsm, silc_net_connect_st_finish);
+ return SILC_FSM_CONTINUE;
+ }
+
+ if (conn->stream_status != SILC_SOCKET_OK) {
+ /** Stream creation failed */
+ if (conn->stream_status == SILC_SOCKET_UNKNOWN_IP)
+ conn->status = SILC_NET_UNKNOWN_IP;
+ else if (conn->stream_status == SILC_SOCKET_UNKNOWN_HOST)
+ conn->status = SILC_NET_UNKNOWN_HOST;
+ else
+ conn->status = SILC_NET_ERROR;
+ silc_fsm_next(fsm, silc_net_connect_st_finish);
+ return SILC_FSM_CONTINUE;
+ }
+
+ /* Set stream information */
+ silc_socket_stream_set_info(conn->stream,
+ !silc_net_is_ip(conn->remote) ? conn->remote :
+ conn->ip_addr, conn->ip_addr, conn->port);
+
+ /** Stream created successfully */
+ SILC_LOG_DEBUG(("Connected successfully"));
+ conn->status = SILC_NET_OK;
+ silc_fsm_next(fsm, silc_net_connect_st_finish);
+ return SILC_FSM_CONTINUE;
+}
+
+SILC_FSM_STATE(silc_net_connect_st_finish)
+{
+ SilcNetConnect conn = fsm_context;
+
+ /* Deliver error or new stream */
+ if (!conn->aborted) {
+ conn->callback(conn->status, conn->stream, conn->context);
+ if (conn->op)
+ silc_async_free(conn->op);
+ if (conn->sop)
+ silc_async_free(conn->sop);
+ }
+
+ return SILC_FSM_FINISH;
+}
+
+static void silc_net_connect_abort(SilcAsyncOperation op, void *context)
+{
+ SilcNetConnect conn = context;
+ conn->aborted = TRUE;
+
+ /* Abort underlaying stream creation too */
+ if (conn->sop)
+ silc_async_abort(conn->op, NULL, NULL);
+}
+
+static void silc_net_connect_destructor(SilcFSM fsm, void *fsm_context,
+ void *destructor_context)
+{
+ SilcNetConnect conn = fsm_context;
+ silc_free(conn->local_ip);
+ silc_free(conn->remote);
+ silc_free(conn);
+}
+
+/* Create asynchronous TCP/IP connection. */
+
+SilcAsyncOperation silc_net_tcp_connect(const char *local_ip_addr,
+ const char *remote_ip_addr,
+ int remote_port,
+ SilcSchedule schedule,
+ SilcNetCallback callback,
+ void *context)
+{
+ SilcNetConnect conn;
+
+ if (!remote_ip_addr || remote_port < 1 || !schedule || !callback)
+ return NULL;
+
+ SILC_LOG_DEBUG(("Creating connection to host %s port %d",
+ remote_ip_addr, remote_port));
+
+ conn = silc_calloc(1, sizeof(*conn));
+ if (!conn) {
+ callback(SILC_NET_NO_MEMORY, NULL, context);
+ return NULL;
+ }
+
+ /* Start async operation */
+ conn->op = silc_async_alloc(silc_net_connect_abort, NULL, conn);
+ if (!conn->op) {
+ callback(SILC_NET_NO_MEMORY, NULL, context);
+ return NULL;
+ }
+
+ if (local_ip_addr)
+ conn->local_ip = strdup(local_ip_addr);
+ conn->remote = strdup(remote_ip_addr);
+ if (!conn->remote) {
+ callback(SILC_NET_NO_MEMORY, NULL, context);
+ return NULL;
+ }
+ conn->port = remote_port;
+ conn->callback = callback;
+ conn->context = context;
+ conn->retry = 1;
+ conn->status = SILC_NET_ERROR;
+
+ silc_fsm_init(&conn->fsm, conn, silc_net_connect_destructor, NULL, schedule);
+ silc_fsm_start(&conn->fsm, silc_net_connect_st_start);
+
+ return conn->op;