terom@178: #include "tcp_internal.h" terom@178: #include "sock_internal.h" terom@178: #include "log.h" terom@178: terom@178: #include terom@178: terom@178: /* terom@178: * Service methods terom@178: */ terom@178: void tcp_server__deinit (service_t *service) terom@178: { terom@178: struct tcp_server *serv = service_check(service, &tcp_server_type); terom@178: terom@178: tcp_server_deinit(serv); terom@178: } terom@178: terom@178: /* terom@178: * Service type terom@178: */ terom@178: const struct service_type tcp_server_type = { terom@178: .base_type = { terom@185: .parent = &service_type_type, terom@178: }, terom@178: .methods = { terom@178: .deinit = tcp_server__deinit, terom@178: }, terom@178: }; terom@178: terom@178: /* terom@178: * We got a new client, build a transport for it and give it to the user terom@178: */ terom@178: static err_t tcp_server_client (struct tcp_server *serv, evutil_socket_t sock, error_t *err) terom@178: { terom@178: struct tcp_transport *trans; terom@178: terom@178: // create a new transport for it, this also makes it nonblocking terom@178: if (tcp_transport_create(&trans, &serv->base_service.info.trans_info, sock, err)) terom@178: goto error; terom@178: terom@178: // make it connected terom@178: // this will call transport_callbacks::on_connect, which is all the user needs terom@178: if (tcp_transport_connected(trans, err)) terom@178: goto error; terom@178: terom@178: // ok terom@178: return SUCCESS; terom@178: terom@178: error: terom@178: // cleanup terom@178: if (trans) terom@178: tcp_transport_destroy(trans); terom@178: terom@178: return ERROR_CODE(err); terom@178: } terom@178: terom@178: /* terom@178: * Libevent callback terom@178: */ terom@178: static void tcp_server_on_accept (evutil_socket_t sock, short what, void *arg) terom@178: { terom@178: struct tcp_server *serv = arg; terom@178: evutil_socket_t client_sock; terom@178: error_t err; terom@178: terom@178: (void) what; terom@178: terom@178: // accept as a new client connection terom@178: if ((client_sock = accept(sock, NULL, NULL)) < 0 && errno != EAGAIN) terom@178: JUMP_SET_ERROR_ERRNO(&err, ERR_ACCEPT); terom@178: terom@178: // spurious read event? terom@178: if (client_sock < 0) terom@178: return; terom@178: terom@178: // handle it terom@178: if (tcp_server_client(serv, client_sock, &err)) terom@178: goto error; terom@178: terom@178: // ok terom@178: return; terom@178: terom@178: error: terom@178: if (client_sock >= 0) terom@178: EVUTIL_CLOSESOCKET(client_sock); terom@178: terom@178: // faaail terom@178: service_error(&serv->base_service, &err); terom@178: } terom@178: terom@178: /* terom@178: * Attempts to construct a listen()'d socket with the given addr, and return it terom@178: * terom@178: * @param addr the addrinfo to try and create a socket for terom@178: * @param err returned error info terom@178: * @return listening socket, or -err_t on error terom@178: */ terom@178: static int tcp_server_sock_addr (struct addrinfo *addr, error_t *err) terom@178: { terom@178: evutil_socket_t sock; terom@178: terom@178: // create the sock terom@185: if ((sock = tcp_sock_create(addr, err)) < 0) terom@178: goto error; terom@178: terom@178: // bind it terom@178: if (bind(sock, addr->ai_addr, addr->ai_addrlen) < 0) terom@178: JUMP_SET_ERROR_ERRNO(err, ERR_BIND); terom@178: terom@178: // listen terom@178: if (listen(sock, TCP_SERVER_BACKLOG) < 0) terom@178: JUMP_SET_ERROR_ERRNO(err, ERR_LISTEN); terom@178: terom@178: // ok, valid socket terom@178: return sock; terom@178: terom@178: error: terom@178: if (sock >= 0) terom@178: // cleanup terom@178: EVUTIL_CLOSESOCKET(sock); terom@178: terom@178: return -ERROR_CODE(err); terom@178: } terom@178: terom@178: /* terom@178: * Construct a listen()'d socket with the given resolver result, and return it. terom@178: * terom@178: * @param rr the resolver lookup result to create a socket for terom@178: * @param err returned error info terom@178: * @return listening socket, or -err_t on error terom@178: */ terom@178: static int tcp_server_sock (struct resolve_result *rr, error_t *err) terom@178: { terom@178: struct addrinfo *addr; terom@178: evutil_socket_t sock; terom@178: terom@178: // try each addrinfo terom@178: while ((addr = resolve_result_next(rr))) { terom@178: // attempt to construct given socket terom@178: if ((sock = tcp_server_sock_addr(addr, err)) < 0) terom@178: // log an informative error warning terom@178: log_warn_error(err, "%s", resolve_addr_text(addr)); terom@178: terom@178: else terom@178: // got a valid socket terom@178: break; terom@178: } terom@178: terom@178: if (sock >= 0) terom@178: // valid socket terom@178: return sock; terom@178: terom@178: else terom@178: // some error occured terom@178: return -ERROR_CODE(err); terom@178: } terom@178: terom@178: err_t tcp_server_listen (struct tcp_server *serv, const char *interface, const char *service, error_t *err) terom@178: { terom@178: struct resolve_result rr; terom@178: evutil_socket_t sock; terom@178: terom@178: // get the global event_base terom@178: struct event_base *ev_base = _sock_stream_ctx.ev_base; terom@178: terom@178: // init the resolver terom@178: resolve_result_init(&rr); terom@178: terom@178: // resolve the interface/service terom@178: if (resolve_addr(&rr, interface, service, SOCK_STREAM, AI_PASSIVE, err)) terom@178: return ERROR_CODE(err); terom@178: terom@178: // create the socket terom@178: if ((sock = tcp_server_sock(&rr, err)) < 0) terom@178: goto error; terom@178: terom@192: // deinit lookup results terom@192: resolve_result_deinit(&rr); terom@192: terom@178: // make it nonblocking terom@178: if (evutil_make_socket_nonblocking(sock)) terom@178: JUMP_SET_ERROR_STR(err, ERR_MISC, "evutil_make_socket_nonblocking"); terom@178: terom@178: // construct event for the sock terom@178: if ((serv->ev = event_new(ev_base, sock, EV_READ | EV_PERSIST, tcp_server_on_accept, serv)) == NULL) terom@178: JUMP_SET_ERROR(err, ERR_EVENT_NEW); terom@178: terom@178: // add it terom@178: if (event_add(serv->ev, NULL)) terom@178: JUMP_SET_ERROR(err, ERR_EVENT_ADD); terom@178: terom@178: // ok terom@178: return SUCCESS; terom@178: terom@178: error: terom@192: // deinit results just to be sure terom@192: resolve_result_deinit(&rr); terom@192: terom@178: if (sock >= 0 && !serv->ev) terom@178: // need to close socket ourselves, because we couldn't register our event for it terom@178: EVUTIL_CLOSESOCKET(sock); terom@178: terom@178: // general cleanup terom@178: tcp_server_deinit(serv); terom@178: terom@178: return ERROR_CODE(err); terom@178: } terom@178: terom@178: void tcp_server_deinit (struct tcp_server *serv) terom@178: { terom@178: if (serv->ev) { terom@178: // ignore errors terom@178: event_del(serv->ev); terom@178: terom@178: // ignore errors terom@178: close(event_get_fd(serv->ev)); terom@178: terom@178: // release event terom@178: event_free(serv->ev); terom@178: terom@178: // invalidate terom@178: serv->ev = NULL; terom@178: } terom@178: } terom@178: terom@178: static void tcp_server_destroy (struct tcp_server *serv) terom@178: { terom@178: tcp_server_deinit(serv); terom@178: terom@178: free(serv); terom@178: } terom@178: terom@178: /* terom@178: * Public interface terom@178: */ terom@178: err_t tcp_listen (const struct service_info *info, service_t **service_ptr, terom@178: const char *interface, const char *service, error_t *err) terom@178: { terom@178: struct tcp_server *serv; terom@178: terom@178: // alloc terom@178: if ((serv = calloc(1, sizeof(*serv))) == NULL) terom@178: return SET_ERROR(err, ERR_MEM); terom@178: terom@178: // init service terom@178: service_init(&serv->base_service, &tcp_server_type, info); terom@178: terom@178: // init ourselves terom@178: if (tcp_server_listen(serv, interface, service, err)) terom@178: goto error; terom@178: terom@178: // ok terom@178: *service_ptr = &serv->base_service; terom@178: terom@185: return SUCCESS; terom@185: terom@178: error: terom@178: tcp_server_destroy(serv); terom@178: terom@178: return ERROR_CODE(err); terom@178: }