X-Git-Url: https://git.sesse.net/?p=cubemap;a=blobdiff_plain;f=server.cpp;h=78273b00eb7f3b9b49938c376eba4aeb92fe4300;hp=a7f579c170928eabae1ca0d025c5ec1204741dd5;hb=f2e7dbf218365e3f47b942ea999796b2724ccc24;hpb=014604812e65d325316778ac98ac7c422226fce8 diff --git a/server.cpp b/server.cpp index a7f579c..78273b0 100644 --- a/server.cpp +++ b/server.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -18,6 +20,7 @@ #include #include +#include "ktls.h" #include "tlse.h" #include "acceptor.h" @@ -207,16 +210,7 @@ void Server::add_client(int sock, Acceptor *acceptor) assert(inserted.second == true); // Should not already exist. Client *client_ptr = &inserted.first->second; - // Connection timestamps must be nondecreasing. I can't find any guarantee - // that even the monotonic clock can't go backwards by a small amount - // (think switching between CPUs with non-synchronized TSCs), so if - // this actually should happen, we hack around it by fudging - // connect_time. - if (!clients_ordered_by_connect_time.empty() && - is_earlier(client_ptr->connect_time, clients_ordered_by_connect_time.back().first)) { - client_ptr->connect_time = clients_ordered_by_connect_time.back().first; - } - clients_ordered_by_connect_time.push(make_pair(client_ptr->connect_time, sock)); + start_client_timeout_timer(client_ptr); // Start listening on data from this socket. epoll_event ev; @@ -299,9 +293,27 @@ void Server::add_client_from_serialized(const ClientProto &client, const vector< } } +void Server::start_client_timeout_timer(Client *client) +{ + // Connection timestamps must be nondecreasing. I can't find any guarantee + // that even the monotonic clock can't go backwards by a small amount + // (think switching between CPUs with non-synchronized TSCs), so if + // this actually should happen, we hack around it by fudging + // connect_time. + if (clock_gettime(CLOCK_MONOTONIC_COARSE, &client->connect_time) == -1) { + log_perror("clock_gettime(CLOCK_MONOTONIC_COARSE)"); + } else { + if (!clients_ordered_by_connect_time.empty() && + is_earlier(client->connect_time, clients_ordered_by_connect_time.back().first)) { + client->connect_time = clients_ordered_by_connect_time.back().first; + } + clients_ordered_by_connect_time.push(make_pair(client->connect_time, client->sock)); + } +} + int Server::lookup_stream_by_url(const string &url) const { - map::const_iterator stream_url_it = stream_url_map.find(url); + const auto stream_url_it = stream_url_map.find(url); if (stream_url_it == stream_url_map.end()) { return -1; } @@ -746,6 +758,22 @@ sending_data_again: } } +namespace { + +void flush_pending_data(int sock) +{ + // Flush pending data, which would otherwise wait for the 200ms TCP_CORK timer + // to elapsed; does not cancel out TCP_CORK (since that still takes priority), + // but does a one-off flush. + int one = 1; + if (setsockopt(sock, SOL_TCP, TCP_NODELAY, &one, sizeof(one)) == -1) { + log_perror("setsockopt(TCP_NODELAY)"); + // Can still continue. + } +} + +} // namespace + bool Server::send_pending_tls_data(Client *client) { // See if there's data from the TLS library to write. @@ -778,9 +806,15 @@ send_data_again: return true; } if (ret > 0 && size_t(ret) == client->tls_data_left_to_send) { - // All data has been sent, so we don't need to go to sleep. + // All data has been sent, so we don't need to go to sleep + // (although we are likely to do so immediately afterwards, + // due to lack of client data). tls_buffer_clear(client->tls_context); client->tls_data_to_send = nullptr; + + // Flush the data we just wrote, since the client probably + // is waiting for it. + flush_pending_data(client->sock); return false; } @@ -889,7 +923,7 @@ void Server::skip_lost_data(Client *client) if (!client->close_after_response) { assert(client->stream_pos_end != Client::STREAM_POS_NO_END); - // We've already sent a Content-length, so we can't just skip data. + // We've already sent a Content-Length, so we can't just skip data. // Close the connection immediately and hope the other side // is able to figure out that there was an error and it needs to skip. client->close_after_response = true; @@ -909,13 +943,12 @@ int Server::parse_request(Client *client) } // Parse the headers, for logging purposes. - // TODO: Case-insensitivity. - multimap headers = extract_headers(lines, client->remote_addr); - multimap::const_iterator referer_it = headers.find("Referer"); + HTTPHeaderMultimap headers = extract_headers(lines, client->remote_addr); + const auto referer_it = headers.find("Referer"); if (referer_it != headers.end()) { client->referer = referer_it->second; } - multimap::const_iterator user_agent_it = headers.find("User-Agent"); + const auto user_agent_it = headers.find("User-Agent"); if (user_agent_it != headers.end()) { client->user_agent = user_agent_it->second; } @@ -985,25 +1018,25 @@ int Server::parse_request(Client *client) client->close_after_response = true; client->http_11 = false; } else { - multimap::const_iterator connection_it = headers.find("Connection"); + const auto connection_it = headers.find("Connection"); if (connection_it != headers.end() && connection_it->second == "close") { client->close_after_response = true; } } - map::const_iterator stream_url_map_it = stream_url_map.find(url); + const auto stream_url_map_it = stream_url_map.find(url); if (stream_url_map_it != stream_url_map.end()) { // Serve a regular stream.. client->stream = streams[stream_url_map_it->second].get(); client->serving_hls_playlist = false; } else { - map::const_iterator stream_hls_url_map_it = stream_hls_url_map.find(url); + const auto stream_hls_url_map_it = stream_hls_url_map.find(url); if (stream_hls_url_map_it != stream_hls_url_map.end()) { // Serve HLS playlist. client->stream = streams[stream_hls_url_map_it->second].get(); client->serving_hls_playlist = true; } else { - map::const_iterator ping_url_map_it = ping_url_map.find(url); + const auto ping_url_map_it = ping_url_map.find(url); if (ping_url_map_it == ping_url_map.end()) { return 404; // Not found. } else { @@ -1065,11 +1098,11 @@ void Server::construct_stream_header(Client *client) string response = stream->http_header; if (client->stream_pos == Client::STREAM_POS_HEADER_ONLY) { char buf[64]; - snprintf(buf, sizeof(buf), "Content-length: %zu\r\n", stream->stream_header.size()); + snprintf(buf, sizeof(buf), "Content-Length: %zu\r\n", stream->stream_header.size()); response.append(buf); } else if (client->stream_pos_end != Client::STREAM_POS_NO_END) { char buf[64]; - snprintf(buf, sizeof(buf), "Content-length: %zu\r\n", client->stream_pos_end - client->stream_pos); + snprintf(buf, sizeof(buf), "Content-Length: %" PRIu64 "\r\n", client->stream_pos_end - client->stream_pos); response.append(buf); } if (client->http_11) { @@ -1089,7 +1122,7 @@ void Server::construct_stream_header(Client *client) if (stream->encoding == Stream::STREAM_ENCODING_RAW) { response.append("\r\n"); } else if (stream->encoding == Stream::STREAM_ENCODING_METACUBE) { - response.append("Content-encoding: metacube\r\n\r\n"); + response.append("Content-Encoding: metacube\r\n\r\n"); if (!stream->stream_header.empty()) { metacube2_block_header hdr; memcpy(hdr.sync, METACUBE2_SYNC, sizeof(hdr.sync)); @@ -1123,11 +1156,11 @@ void Server::construct_error(Client *client, int error_code) char error[256]; if (client->http_11 && client->close_after_response) { snprintf(error, sizeof(error), - "HTTP/1.1 %d Error\r\nContent-type: text/plain\r\nConnection: close\r\n\r\nSomething went wrong. Sorry.\r\n", + "HTTP/1.1 %d Error\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nSomething went wrong. Sorry.\r\n", error_code); } else { snprintf(error, sizeof(error), - "HTTP/1.%d %d Error\r\nContent-type: text/plain\r\nContent-length: 30\r\n\r\nSomething went wrong. Sorry.\r\n", + "HTTP/1.%d %d Error\r\nContent-Type: text/plain\r\nContent-Length: 30\r\n\r\nSomething went wrong. Sorry.\r\n", client->http_11, error_code); } client->header_or_short_response_holder = error; @@ -1166,7 +1199,7 @@ void Server::construct_hls_playlist(Client *client) void Server::construct_204(Client *client) { - map::const_iterator ping_url_map_it = ping_url_map.find(client->url); + const auto ping_url_map_it = ping_url_map.find(client->url); assert(ping_url_map_it != ping_url_map.end()); string response; @@ -1194,12 +1227,49 @@ void Server::construct_204(Client *client) change_epoll_events(client, EPOLLOUT | EPOLLET | EPOLLRDHUP); } +namespace { + template void delete_from(vector *v, T elem) { typename vector::iterator new_end = remove(v->begin(), v->end(), elem); v->erase(new_end, v->end()); } + +void send_ktls_close(int sock) +{ + uint8_t record_type = 21; // Alert. + uint8_t body[] = { + 1, // Warning level (but still fatal!). + 0, // close_notify. + }; + + int cmsg_len = sizeof(record_type); + char buf[CMSG_SPACE(cmsg_len)]; + + msghdr msg = {0}; + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_TLS; + cmsg->cmsg_type = TLS_SET_RECORD_TYPE; + cmsg->cmsg_len = CMSG_LEN(cmsg_len); + *CMSG_DATA(cmsg) = record_type; + msg.msg_controllen = cmsg->cmsg_len; + + iovec msg_iov; + msg_iov.iov_base = body; + msg_iov.iov_len = sizeof(body); + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + int err; + do { + err = sendmsg(sock, &msg, 0); + } while (err == -1 && errno == EINTR); // Ignore all other errors. +} + +} // namespace void Server::close_client(Client *client) { @@ -1215,6 +1285,10 @@ void Server::close_client(Client *client) } if (client->tls_context) { + if (client->in_ktls_mode) { + // Keep GnuTLS happy. + send_ktls_close(client->sock); + } tls_destroy_context(client->tls_context); } @@ -1248,13 +1322,7 @@ bool Server::more_requests(Client *client) // Log to access_log. access_log->write(client->get_stats()); - // Flush pending data; does not cancel out TCP_CORK (since that still takes priority), - // but does a one-off flush. - int one = 1; - if (setsockopt(client->sock, SOL_TCP, TCP_NODELAY, &one, sizeof(one)) == -1) { - log_perror("setsockopt(TCP_NODELAY)"); - // Can still continue. - } + flush_pending_data(client->sock); // Switch states and reset the parsers. We don't reset statistics. client->state = Client::READING_REQUEST; @@ -1264,6 +1332,7 @@ bool Server::more_requests(Client *client) client->header_or_short_response_holder.clear(); client->header_or_short_response_ref.reset(); client->header_or_short_response_bytes_sent = 0; + start_client_timeout_timer(client); change_epoll_events(client, EPOLLIN | EPOLLET | EPOLLRDHUP); // No TLS handshake, so no EPOLLOUT needed.