src/lib/tcp_client.c
branchnew-lib-errors
changeset 219 cefec18b8268
parent 177 a74b55104fb9
equal deleted inserted replaced
218:5229a5d098b2 219:cefec18b8268
       
     1 #include "tcp_internal.h"
       
     2 #include "log.h"
       
     3 
       
     4 /*
       
     5  * Our transport methods
       
     6  */
       
     7 static void tcp_client__deinit (transport_t *transport)
       
     8 {
       
     9     struct tcp_client *client = transport_check(transport, &tcp_client_type);
       
    10     
       
    11     // proxy
       
    12     tcp_client_deinit(client);
       
    13 }
       
    14 
       
    15 /*
       
    16  * Our transport_type
       
    17  */
       
    18 const struct transport_type tcp_client_type = {
       
    19     .base_type = {
       
    20         .parent             = &tcp_transport_type.base_type,
       
    21     },
       
    22     .methods                = {
       
    23         .read               = transport_fd__read,
       
    24         .write              = transport_fd__write,
       
    25         .events             = transport_fd__events,
       
    26         .deinit             = tcp_client__deinit,
       
    27     },
       
    28 };
       
    29 
       
    30 /*
       
    31  * Forward-declare 
       
    32  */
       
    33 static void tcp_client_on_connect (struct transport_fd *fd, short what, void *arg);
       
    34 
       
    35 /*
       
    36  * Function implementations
       
    37  */
       
    38 void tcp_client_init (struct tcp_client *client)
       
    39 {
       
    40     tcp_transport_init(&client->base_trans, -1);
       
    41     
       
    42     resolve_result_init(&client->rr);
       
    43 }
       
    44 
       
    45 /*
       
    46  * Start connecting to the given address in a non-blocking fashion. Returns any errors that immediately crop up,
       
    47  * otherwise eventually calls tcp_client_connect_done().
       
    48  */
       
    49 static err_t tcp_client_connect_addr (struct tcp_client *client, struct addrinfo *addr, error_t *err)
       
    50 {
       
    51     struct transport_fd *_fd = &client->base_trans.base_fd;
       
    52     int ret;
       
    53     evutil_socket_t sock;
       
    54     err_t tmp;
       
    55 
       
    56     // first, create the socket
       
    57     if ((sock = tcp_sock_create(addr, err)) < 0)
       
    58         return ERROR_CODE(err);
       
    59 
       
    60     // set it as our sock
       
    61     if ((ERROR_CODE(err) = transport_fd_set(_fd, sock)))
       
    62         goto error;
       
    63 
       
    64     // then, set it up as nonblocking
       
    65     if ((ERROR_CODE(err) = transport_fd_nonblock(_fd, true)))
       
    66         goto error;
       
    67 
       
    68     // then, initiate the connect
       
    69     if ((ret = connect(sock, addr->ai_addr, addr->ai_addrlen)) < 0 && errno != EINPROGRESS) 
       
    70         JUMP_SET_ERROR_ERRNO(err, ERR_CONNECT);
       
    71     
       
    72     if (ret < 0) {
       
    73         // ok, connect started, setup our completion callback
       
    74         if ((ERROR_CODE(err) = transport_fd_setup(_fd, tcp_client_on_connect, client)))
       
    75             goto error;
       
    76     
       
    77         // enable for write
       
    78         if ((ERROR_CODE(err) = transport_fd_enable(_fd, TRANSPORT_WRITE)))
       
    79             goto error;
       
    80 
       
    81     } else {
       
    82         // oops... blocking connect - fail to avoid confusion
       
    83         // XXX: come up with a better error name to use
       
    84         // XXX: support non-async connects as well
       
    85         JUMP_SET_ERROR_EXTRA(err, ERR_CONNECT, EINPROGRESS);
       
    86     }
       
    87     
       
    88     // ok
       
    89     return SUCCESS;
       
    90 
       
    91 error:
       
    92     // close the stuff we did open
       
    93     if ((tmp = transport_fd_close(_fd)))
       
    94         log_warn("error closing socket after connect error: %s", error_name(tmp));
       
    95 
       
    96     return ERROR_CODE(err);
       
    97 }
       
    98 
       
    99 
       
   100 /*
       
   101  * Attempt to connect to the next addrinfo, or the next one, if that fails, etc.
       
   102  *
       
   103  * This does not call transport_connected().
       
   104  */
       
   105 static err_t tcp_client_connect_continue (struct tcp_client *client, error_t *err)
       
   106 {
       
   107     struct addrinfo *addr;
       
   108 
       
   109     // try and connect to each one until we find one that works
       
   110     while ((addr = resolve_result_next(&client->rr))) {
       
   111         // attempt to start connect
       
   112         if (tcp_client_connect_addr(client, addr, err) == SUCCESS)
       
   113             break;
       
   114 
       
   115         // log a warning on the failed connect
       
   116         log_warn_error(err, "%s", resolve_addr_text(addr));
       
   117     }
       
   118     
       
   119 
       
   120     if (addr)
       
   121         // we succesfully did a tcp_client_connect_addr on valid address
       
   122         return SUCCESS;
       
   123 
       
   124     else
       
   125         // all of the connect_async_addr's failed, return the last error
       
   126         return ERROR_CODE(err);
       
   127 }
       
   128 
       
   129 /*
       
   130  * Cleanup our resolver state and any connect callbacks after a connect
       
   131  */
       
   132 static void tcp_client_connect_cleanup (struct tcp_client *client)
       
   133 {
       
   134     // drop the resolver stuff
       
   135     resolve_result_deinit(&client->rr);
       
   136     
       
   137     // remove our event handler
       
   138     transport_fd_clear(&client->base_trans.base_fd);
       
   139 }
       
   140 
       
   141 /*
       
   142  * Our async connect operation has completed, clean up, set up state for event-based operation with user callbacks, and
       
   143  * invoke transport_connected().
       
   144  *
       
   145  * The given \a err should be NULL for successful completion, or the error for failures.
       
   146  */
       
   147 static void tcp_client_connect_done (struct tcp_client *client, error_t *conn_err)
       
   148 {
       
   149     error_t err;
       
   150 
       
   151     // cleanup
       
   152     tcp_client_connect_cleanup(client);
       
   153 
       
   154     if (conn_err)
       
   155         JUMP_SET_ERROR_INFO(&err, conn_err);
       
   156 
       
   157     // let the transport handle the rest
       
   158     if (tcp_transport_connected(&client->base_trans, &err))
       
   159         goto error;
       
   160     
       
   161     // ok
       
   162     return;
       
   163 
       
   164 error:    
       
   165     // pass the error on to transport
       
   166     transport_connected(&client->base_trans.base_fd.base, &err, false);
       
   167 }
       
   168 
       
   169 /*
       
   170  * Our async connect callback
       
   171  */
       
   172 static void tcp_client_on_connect (struct transport_fd *fd, short what, void *arg)
       
   173 {
       
   174     struct tcp_client *client = arg;
       
   175     error_t err;
       
   176     err_t tmp;
       
   177 
       
   178     // XXX: timeouts
       
   179     (void) what;
       
   180     
       
   181     // read socket error code
       
   182     if (tcp_sock_error(client->base_trans.base_fd.fd, &err))
       
   183         goto error;
       
   184 
       
   185     // did the connect fail?
       
   186     if (ERROR_EXTRA(&err))
       
   187         JUMP_SET_ERROR(&err, ERR_CONNECT);
       
   188     
       
   189     // done, success
       
   190     return tcp_client_connect_done(client, NULL);
       
   191 
       
   192 error:
       
   193     // close the socket
       
   194     if ((tmp = transport_fd_close(fd)))
       
   195         log_warn("error closing socket after connect error: %s", error_name(tmp));
       
   196 
       
   197     // log a warning
       
   198     log_warn_error(&err, "connect to %s failed", "???");
       
   199 
       
   200     // try the next one or fail completely
       
   201     if (tcp_client_connect_continue(client, &err))
       
   202         tcp_client_connect_done(client, &err);
       
   203 }
       
   204 
       
   205 err_t tcp_client_connect_async (struct tcp_client *client, const char *hostname, const char *service, error_t *err)
       
   206 {
       
   207     // do the resolving
       
   208     if (resolve_addr(&client->rr, hostname, service, SOCK_STREAM, 0, err))
       
   209         return ERROR_CODE(err);
       
   210 
       
   211     // start connecting with the first result
       
   212     if (tcp_client_connect_continue(client, err))
       
   213         goto error;
       
   214 
       
   215     // ok
       
   216     return SUCCESS;
       
   217 
       
   218 error:
       
   219     // cleanup
       
   220     resolve_result_deinit(&client->rr);
       
   221     
       
   222     return ERROR_CODE(err);
       
   223 }
       
   224 
       
   225 void tcp_client_deinit (struct tcp_client *client)
       
   226 {
       
   227     // cleanup our stuff
       
   228     resolve_result_deinit(&client->rr);
       
   229     
       
   230     // deinit lower transport
       
   231     tcp_transport_deinit(&client->base_trans);
       
   232 }
       
   233 
       
   234 /*
       
   235  * Deinit and free, not using the transport interface
       
   236  */
       
   237 static void tcp_client_destroy (struct tcp_client *client)
       
   238 {
       
   239     tcp_client_deinit(client);
       
   240 
       
   241     free(client);
       
   242 }
       
   243 
       
   244 /*
       
   245  * Public interface
       
   246  */
       
   247 err_t tcp_connect (const struct transport_info *info, transport_t **transport_ptr, 
       
   248         const char *host, const char *service, error_t *err)
       
   249 {
       
   250     struct tcp_client *client;
       
   251  
       
   252     // alloc
       
   253     if ((client = calloc(1, sizeof(*client))) == NULL)
       
   254         return ERR_CALLOC;
       
   255 
       
   256     // init transport
       
   257     transport_init(&client->base_trans.base_fd.base, &tcp_client_type, info);
       
   258     
       
   259     // init our state
       
   260     tcp_client_init(client);
       
   261  
       
   262     // begin connect
       
   263     if (tcp_client_connect_async(client, host, service, err))
       
   264         goto error;
       
   265 
       
   266     // good
       
   267     *transport_ptr = &client->base_trans.base_fd.base;
       
   268 
       
   269     return 0;
       
   270 
       
   271 error:
       
   272     // cleanup
       
   273     tcp_client_destroy(client);
       
   274         
       
   275     // return error code
       
   276     return ERROR_CODE(err);
       
   277 }
       
   278