Serenity Operating System
at master 338 lines 12 kB view raw
1/* 2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "LookupServer.h" 8#include "ConnectionFromClient.h" 9#include <AK/Debug.h> 10#include <AK/DeprecatedString.h> 11#include <AK/HashMap.h> 12#include <AK/Random.h> 13#include <AK/StringBuilder.h> 14#include <LibCore/ConfigFile.h> 15#include <LibCore/DeprecatedFile.h> 16#include <LibCore/LocalServer.h> 17#include <LibDNS/Packet.h> 18#include <limits.h> 19#include <stdio.h> 20#include <time.h> 21#include <unistd.h> 22 23namespace LookupServer { 24 25static LookupServer* s_the; 26// NOTE: This is the TTL we return for the hostname or answers from /etc/hosts. 27static constexpr u32 s_static_ttl = 86400; 28 29LookupServer& LookupServer::the() 30{ 31 VERIFY(s_the); 32 return *s_the; 33} 34 35LookupServer::LookupServer() 36{ 37 VERIFY(s_the == nullptr); 38 s_the = this; 39 40 auto config = Core::ConfigFile::open_for_system("LookupServer").release_value_but_fixme_should_propagate_errors(); 41 dbgln("Using network config file at {}", config->filename()); 42 m_nameservers = config->read_entry("DNS", "Nameservers", "1.1.1.1,1.0.0.1").split(','); 43 44 load_etc_hosts(); 45 46 auto maybe_file_watcher = Core::FileWatcher::create(); 47 // NOTE: If this happens during startup, something is very wrong. 48 if (maybe_file_watcher.is_error()) { 49 dbgln("Core::FileWatcher::create(): {}", maybe_file_watcher.error()); 50 VERIFY_NOT_REACHED(); 51 } 52 m_file_watcher = maybe_file_watcher.release_value(); 53 54 m_file_watcher->on_change = [this](auto&) { 55 dbgln("Reloading '/etc/hosts' because it was changed."); 56 load_etc_hosts(); 57 }; 58 59 auto result = m_file_watcher->add_watch("/etc/hosts", Core::FileWatcherEvent::Type::ContentModified | Core::FileWatcherEvent::Type::Deleted); 60 // NOTE: If this happens during startup, something is very wrong. 61 if (result.is_error()) { 62 dbgln("Core::FileWatcher::add_watch(): {}", result.error()); 63 VERIFY_NOT_REACHED(); 64 } else if (!result.value()) { 65 dbgln("Core::FileWatcher::add_watch(): {}", result.value()); 66 VERIFY_NOT_REACHED(); 67 } 68 69 if (config->read_bool_entry("DNS", "EnableServer")) { 70 m_dns_server = DNSServer::construct(this); 71 // TODO: drop root privileges here. 72 } 73 m_mdns = MulticastDNS::construct(this); 74 75 m_server = MUST(IPC::MultiServer<ConnectionFromClient>::try_create()); 76} 77 78void LookupServer::load_etc_hosts() 79{ 80 m_etc_hosts.clear(); 81 auto add_answer = [this](Name const& name, RecordType record_type, DeprecatedString data) { 82 m_etc_hosts.ensure(name).empend(name, record_type, RecordClass::IN, s_static_ttl, move(data), false); 83 }; 84 85 auto file = Core::DeprecatedFile::construct("/etc/hosts"); 86 if (!file->open(Core::OpenMode::ReadOnly)) { 87 dbgln("Failed to open '/etc/hosts'"); 88 return; 89 } 90 91 u32 line_number = 0; 92 while (!file->eof()) { 93 auto original_line = file->read_line(1024); 94 ++line_number; 95 if (original_line.is_empty()) 96 break; 97 auto trimmed_line = original_line.view().trim_whitespace(); 98 auto replaced_line = trimmed_line.replace(" "sv, "\t"sv, ReplaceMode::All); 99 auto fields = replaced_line.split_view('\t'); 100 101 if (fields.size() < 2) { 102 dbgln("Failed to parse line {} from '/etc/hosts': '{}'", line_number, original_line); 103 continue; 104 } 105 106 if (fields.size() > 2) 107 dbgln("Line {} from '/etc/hosts' ('{}') has more than two parts, only the first two are used.", line_number, original_line); 108 109 auto maybe_address = IPv4Address::from_string(fields[0]); 110 if (!maybe_address.has_value()) { 111 dbgln("Failed to parse line {} from '/etc/hosts': '{}'", line_number, original_line); 112 continue; 113 } 114 115 auto raw_addr = maybe_address->to_in_addr_t(); 116 117 Name name { fields[1] }; 118 add_answer(name, RecordType::A, DeprecatedString { (char const*)&raw_addr, sizeof(raw_addr) }); 119 120 StringBuilder builder; 121 builder.append(maybe_address->to_deprecated_string_reversed()); 122 builder.append(".in-addr.arpa"sv); 123 add_answer(builder.to_deprecated_string(), RecordType::PTR, name.as_string()); 124 } 125} 126 127static DeprecatedString get_hostname() 128{ 129 char buffer[_POSIX_HOST_NAME_MAX]; 130 VERIFY(gethostname(buffer, sizeof(buffer)) == 0); 131 return buffer; 132} 133 134ErrorOr<Vector<Answer>> LookupServer::lookup(Name const& name, RecordType record_type) 135{ 136 dbgln_if(LOOKUPSERVER_DEBUG, "Got request for '{}'", name.as_string()); 137 138 Vector<Answer> answers; 139 auto add_answer = [&](Answer const& answer) { 140 Answer answer_with_original_case { 141 name, 142 answer.type(), 143 answer.class_code(), 144 answer.ttl(), 145 answer.record_data(), 146 answer.mdns_cache_flush(), 147 }; 148 answers.append(answer_with_original_case); 149 }; 150 151 // First, try /etc/hosts. 152 if (auto local_answers = m_etc_hosts.find(name); local_answers != m_etc_hosts.end()) { 153 for (auto& answer : local_answers->value) { 154 if (answer.type() == record_type) 155 add_answer(answer); 156 } 157 if (!answers.is_empty()) 158 return answers; 159 } 160 161 // Second, try the hostname. 162 // NOTE: We don't cache the hostname since it could change during runtime. 163 if (record_type == RecordType::A && get_hostname() == name) { 164 IPv4Address address = { 127, 0, 0, 1 }; 165 auto raw_address = address.to_in_addr_t(); 166 Answer answer { name, RecordType::A, RecordClass::IN, s_static_ttl, DeprecatedString { (char const*)&raw_address, sizeof(raw_address) }, false }; 167 answers.append(move(answer)); 168 return answers; 169 } 170 171 // Third, try our cache. 172 if (auto cached_answers = m_lookup_cache.find(name); cached_answers != m_lookup_cache.end()) { 173 for (auto& answer : cached_answers->value) { 174 // TODO: Actually remove expired answers from the cache. 175 if (answer.type() == record_type && !answer.has_expired()) { 176 dbgln_if(LOOKUPSERVER_DEBUG, "Cache hit: {} -> {}", name.as_string(), answer.record_data()); 177 add_answer(answer); 178 } 179 } 180 if (!answers.is_empty()) 181 return answers; 182 } 183 184 // Fourth, look up .local names using mDNS instead of DNS nameservers. 185 if (name.as_string().ends_with(".local"sv)) { 186 answers = TRY(m_mdns->lookup(name, record_type)); 187 for (auto& answer : answers) 188 put_in_cache(answer); 189 return answers; 190 } 191 192 // Fifth, ask the upstream nameservers. 193 for (auto& nameserver : m_nameservers) { 194 dbgln_if(LOOKUPSERVER_DEBUG, "Doing lookup using nameserver '{}'", nameserver); 195 bool did_get_response = false; 196 int retries = 3; 197 Vector<Answer> upstream_answers; 198 do { 199 auto upstream_answers_or_error = lookup(name, nameserver, did_get_response, record_type); 200 if (upstream_answers_or_error.is_error()) 201 continue; 202 upstream_answers = upstream_answers_or_error.release_value(); 203 if (did_get_response) 204 break; 205 } while (--retries); 206 if (!upstream_answers.is_empty()) { 207 for (auto& answer : upstream_answers) 208 add_answer(answer); 209 break; 210 } else { 211 if (!did_get_response) 212 dbgln("Never got a response from '{}', trying next nameserver", nameserver); 213 else 214 dbgln("Received response from '{}' but no result(s), trying next nameserver", nameserver); 215 } 216 } 217 218 // Sixth, fail. 219 if (answers.is_empty()) { 220 dbgln("Tried all nameservers but never got a response :("); 221 return Vector<Answer> {}; 222 } 223 224 return answers; 225} 226 227ErrorOr<Vector<Answer>> LookupServer::lookup(Name const& name, DeprecatedString const& nameserver, bool& did_get_response, RecordType record_type, ShouldRandomizeCase should_randomize_case) 228{ 229 Packet request; 230 request.set_is_query(); 231 request.set_id(get_random_uniform(UINT16_MAX)); 232 Name name_in_question = name; 233 if (should_randomize_case == ShouldRandomizeCase::Yes) 234 name_in_question.randomize_case(); 235 request.add_question({ name_in_question, record_type, RecordClass::IN, false }); 236 237 auto buffer = TRY(request.to_byte_buffer()); 238 239 auto udp_socket = TRY(Core::UDPSocket::connect(nameserver, 53, Time::from_seconds(1))); 240 TRY(udp_socket->set_blocking(true)); 241 242 TRY(udp_socket->write_until_depleted(buffer)); 243 244 u8 response_buffer[4096]; 245 int nrecv = TRY(udp_socket->read_some({ response_buffer, sizeof(response_buffer) })).size(); 246 if (udp_socket->is_eof()) 247 return Vector<Answer> {}; 248 249 did_get_response = true; 250 251 auto o_response = Packet::from_raw_packet(response_buffer, nrecv); 252 if (!o_response.has_value()) 253 return Vector<Answer> {}; 254 255 auto& response = o_response.value(); 256 257 if (response.id() != request.id()) { 258 dbgln("LookupServer: ID mismatch ({} vs {}) :(", response.id(), request.id()); 259 return Vector<Answer> {}; 260 } 261 262 if (response.code() == Packet::Code::REFUSED) { 263 if (should_randomize_case == ShouldRandomizeCase::Yes) { 264 // Retry with 0x20 case randomization turned off. 265 return lookup(name, nameserver, did_get_response, record_type, ShouldRandomizeCase::No); 266 } 267 return Vector<Answer> {}; 268 } 269 270 if (response.question_count() != request.question_count()) { 271 dbgln("LookupServer: Question count ({} vs {}) :(", response.question_count(), request.question_count()); 272 return Vector<Answer> {}; 273 } 274 275 // Verify the questions in our request and in their response match, ignoring case. 276 for (size_t i = 0; i < request.question_count(); ++i) { 277 auto& request_question = request.questions()[i]; 278 auto& response_question = response.questions()[i]; 279 bool match = request_question.class_code() == response_question.class_code() 280 && request_question.record_type() == response_question.record_type() 281 && request_question.name().as_string().equals_ignoring_ascii_case(response_question.name().as_string()); 282 if (!match) { 283 dbgln("Request and response questions do not match"); 284 dbgln(" Request: name=_{}_, type={}, class={}", request_question.name().as_string(), response_question.record_type(), response_question.class_code()); 285 dbgln(" Response: name=_{}_, type={}, class={}", response_question.name().as_string(), response_question.record_type(), response_question.class_code()); 286 return Vector<Answer> {}; 287 } 288 } 289 290 if (response.answer_count() < 1) { 291 dbgln("LookupServer: No answers :("); 292 return Vector<Answer> {}; 293 } 294 295 Vector<Answer, 8> answers; 296 for (auto& answer : response.answers()) { 297 put_in_cache(answer); 298 if (answer.type() != record_type) 299 continue; 300 answers.append(answer); 301 } 302 303 return answers; 304} 305 306void LookupServer::put_in_cache(Answer const& answer) 307{ 308 if (answer.has_expired()) 309 return; 310 311 // Prevent the cache from growing too big. 312 // TODO: Evict least used entries. 313 if (m_lookup_cache.size() >= 256) 314 m_lookup_cache.remove(m_lookup_cache.begin()); 315 316 auto it = m_lookup_cache.find(answer.name()); 317 if (it == m_lookup_cache.end()) 318 m_lookup_cache.set(answer.name(), { answer }); 319 else { 320 if (answer.mdns_cache_flush()) { 321 auto now = time(nullptr); 322 323 it->value.remove_all_matching([&](Answer const& other_answer) { 324 if (other_answer.type() != answer.type() || other_answer.class_code() != answer.class_code()) 325 return false; 326 327 if (other_answer.received_time() >= now - 1) 328 return false; 329 330 dbgln_if(LOOKUPSERVER_DEBUG, "Removing cache entry: {}", other_answer.name()); 331 return true; 332 }); 333 } 334 it->value.append(answer); 335 } 336} 337 338}