summaryrefslogtreecommitdiff
path: root/Services/LookupServer/LookupServer.cpp
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2020-05-08 21:57:44 +0200
committerAndreas Kling <kling@serenityos.org>2020-05-08 21:57:44 +0200
commitcf3b58fbe8f836c13e44d6152d78960aff6089ef (patch)
treedcc7664f0004ee9c495f9d948cfb16d12f8a70bf /Services/LookupServer/LookupServer.cpp
parent042b1f68145ad3754fd98429b405c5c1a173d3fc (diff)
downloadserenity-cf3b58fbe8f836c13e44d6152d78960aff6089ef.zip
Services: Renamed from Servers
It didn't feel right to have a "DHCPClient" in a "Servers" directory. Rename this to Services to better reflect the type of programs we'll be putting in there.
Diffstat (limited to 'Services/LookupServer/LookupServer.cpp')
-rw-r--r--Services/LookupServer/LookupServer.cpp259
1 files changed, 259 insertions, 0 deletions
diff --git a/Services/LookupServer/LookupServer.cpp b/Services/LookupServer/LookupServer.cpp
new file mode 100644
index 0000000000..fe89bd97fc
--- /dev/null
+++ b/Services/LookupServer/LookupServer.cpp
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "LookupServer.h"
+#include "DNSRequest.h"
+#include "DNSResponse.h"
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/File.h>
+#include <LibCore/LocalServer.h>
+#include <LibCore/LocalSocket.h>
+#include <LibCore/UDPSocket.h>
+#include <stdio.h>
+#include <unistd.h>
+
+LookupServer::LookupServer()
+{
+ auto config = Core::ConfigFile::get_for_system("LookupServer");
+ dbg() << "Using network config file at " << config->file_name();
+ m_nameserver = config->read_entry("DNS", "Nameserver", "1.1.1.1");
+
+ load_etc_hosts();
+
+ m_local_server = Core::LocalServer::construct(this);
+ m_local_server->on_ready_to_accept = [this]() {
+ auto socket = m_local_server->accept();
+ socket->on_ready_to_read = [this, socket]() {
+ service_client(socket);
+ RefPtr<Core::LocalSocket> keeper = socket;
+ const_cast<Core::LocalSocket&>(*socket).on_ready_to_read = [] {};
+ };
+ };
+ bool ok = m_local_server->take_over_from_system_server();
+ ASSERT(ok);
+}
+
+void LookupServer::load_etc_hosts()
+{
+ auto file = Core::File::construct("/etc/hosts");
+ if (!file->open(Core::IODevice::ReadOnly))
+ return;
+ while (!file->eof()) {
+ auto line = file->read_line(1024);
+ if (line.is_empty())
+ break;
+ auto str_line = String((const char*)line.data(), line.size() - 1, Chomp);
+ auto fields = str_line.split('\t');
+
+ auto sections = fields[0].split('.');
+ IPv4Address addr {
+ (u8)atoi(sections[0].characters()),
+ (u8)atoi(sections[1].characters()),
+ (u8)atoi(sections[2].characters()),
+ (u8)atoi(sections[3].characters()),
+ };
+
+ auto name = fields[1];
+ m_etc_hosts.set(name, addr.to_string());
+
+ IPv4Address reverse_addr {
+ (u8)atoi(sections[3].characters()),
+ (u8)atoi(sections[2].characters()),
+ (u8)atoi(sections[1].characters()),
+ (u8)atoi(sections[0].characters()),
+ };
+ StringBuilder builder;
+ builder.append(reverse_addr.to_string());
+ builder.append(".in-addr.arpa");
+ m_etc_hosts.set(builder.to_string(), name);
+ }
+}
+
+void LookupServer::service_client(RefPtr<Core::LocalSocket> socket)
+{
+ u8 client_buffer[1024];
+ int nrecv = socket->read(client_buffer, sizeof(client_buffer) - 1);
+ if (nrecv < 0) {
+ perror("read");
+ return;
+ }
+
+ client_buffer[nrecv] = '\0';
+
+ char lookup_type = client_buffer[0];
+ if (lookup_type != 'L' && lookup_type != 'R') {
+ dbg() << "Invalid lookup_type " << lookup_type;
+ return;
+ }
+ auto hostname = String((const char*)client_buffer + 1, nrecv - 1, Chomp);
+ dbg() << "Got request for '" << hostname << "' (using IP " << m_nameserver << ")";
+
+ Vector<String> responses;
+
+ if (auto known_host = m_etc_hosts.get(hostname); known_host.has_value()) {
+ responses.append(known_host.value());
+ } else if (!hostname.is_empty()) {
+ bool did_timeout;
+ int retries = 3;
+ do {
+ did_timeout = false;
+ if (lookup_type == 'L')
+ responses = lookup(hostname, did_timeout, T_A);
+ else if (lookup_type == 'R')
+ responses = lookup(hostname, did_timeout, T_PTR);
+ if (!did_timeout)
+ break;
+ } while (--retries);
+ if (did_timeout) {
+ fprintf(stderr, "LookupServer: Out of retries :(\n");
+ return;
+ }
+ }
+
+ if (responses.is_empty()) {
+ int nsent = socket->write("Not found.\n");
+ if (nsent < 0)
+ perror("write");
+ return;
+ }
+ for (auto& response : responses) {
+ auto line = String::format("%s\n", response.characters());
+ int nsent = socket->write(line);
+ if (nsent < 0) {
+ perror("write");
+ break;
+ }
+ }
+}
+
+Vector<String> LookupServer::lookup(const String& hostname, bool& did_timeout, unsigned short record_type, ShouldRandomizeCase should_randomize_case)
+{
+ if (auto it = m_lookup_cache.find(hostname); it != m_lookup_cache.end()) {
+ auto& cached_lookup = it->value;
+ if (cached_lookup.question.record_type() == record_type) {
+ Vector<String> responses;
+ for (auto& cached_answer : cached_lookup.answers) {
+ dbg() << "Cache hit: " << hostname << " -> " << cached_answer.record_data() << ", expired: " << cached_answer.has_expired();
+ if (!cached_answer.has_expired()) {
+ responses.append(cached_answer.record_data());
+ }
+ }
+ if (!responses.is_empty())
+ return responses;
+ }
+ m_lookup_cache.remove(it);
+ }
+
+ DNSRequest request;
+ request.add_question(hostname, record_type, should_randomize_case);
+
+ auto buffer = request.to_byte_buffer();
+
+ auto udp_socket = Core::UDPSocket::construct();
+ udp_socket->set_blocking(true);
+
+ struct timeval timeout {
+ 1, 0
+ };
+
+ int rc = setsockopt(udp_socket->fd(), SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
+ if (rc < 0) {
+ perror("setsockopt(SOL_SOCKET, SO_RCVTIMEO)");
+ return {};
+ }
+
+ if (!udp_socket->connect(m_nameserver, 53))
+ return {};
+
+ if (!udp_socket->write(buffer))
+ return {};
+
+ u8 response_buffer[4096];
+ int nrecv = udp_socket->read(response_buffer, sizeof(response_buffer));
+ if (nrecv == 0)
+ return {};
+
+ auto o_response = DNSResponse::from_raw_response(response_buffer, nrecv);
+ if (!o_response.has_value())
+ return {};
+
+ auto& response = o_response.value();
+
+ if (response.id() != request.id()) {
+ dbgprintf("LookupServer: ID mismatch (%u vs %u) :(\n", response.id(), request.id());
+ return {};
+ }
+
+ if (response.code() == DNSResponse::Code::REFUSED) {
+ if (should_randomize_case == ShouldRandomizeCase::Yes) {
+ // Retry with 0x20 case randomization turned off.
+ return lookup(hostname, did_timeout, record_type, ShouldRandomizeCase::No);
+ }
+ return {};
+ }
+
+ if (response.question_count() != request.question_count()) {
+ dbgprintf("LookupServer: Question count (%u vs %u) :(\n", response.question_count(), request.question_count());
+ return {};
+ }
+
+ for (size_t i = 0; i < request.question_count(); ++i) {
+ auto& request_question = request.questions()[i];
+ auto& response_question = response.questions()[i];
+ if (request_question != response_question) {
+ dbg() << "Request and response questions do not match";
+ dbg() << " Request: {_" << request_question.name() << "_, " << request_question.record_type() << ", " << request_question.class_code() << "}";
+ dbg() << " Response: {_" << response_question.name() << "_, " << response_question.record_type() << ", " << response_question.class_code() << "}";
+ return {};
+ }
+ }
+
+ if (response.answer_count() < 1) {
+ dbgprintf("LookupServer: Not enough answers (%u) :(\n", response.answer_count());
+ return {};
+ }
+
+ Vector<String, 8> responses;
+ Vector<DNSAnswer, 8> cacheable_answers;
+ for (auto& answer : response.answers()) {
+ if (answer.type() != T_A)
+ continue;
+ responses.append(answer.record_data());
+ if (!answer.has_expired())
+ cacheable_answers.append(answer);
+ }
+
+ if (!cacheable_answers.is_empty()) {
+ if (m_lookup_cache.size() >= 256)
+ m_lookup_cache.remove(m_lookup_cache.begin());
+ m_lookup_cache.set(hostname, { request.questions()[0], move(cacheable_answers) });
+ }
+ return responses;
+}