From 2424f38a621575487366d6eca2fa07003d062ca3 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Thu, 8 Feb 2024 03:14:00 +0200 Subject: [PATCH] Userspace: Implement super simple DNS resolver in userspace You connect to this service using unix domain sockets and send the asked domain name. It will respond with ip address or 'unavailable' There is no DNS cache implemented so all calls ask the nameserver. --- userspace/CMakeLists.txt | 4 +- userspace/init/main.cpp | 6 + userspace/nslookup/CMakeLists.txt | 16 +++ userspace/nslookup/main.cpp | 47 +++++++ userspace/resolver/CMakeLists.txt | 16 +++ userspace/resolver/main.cpp | 218 ++++++++++++++++++++++++++++++ 6 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 userspace/nslookup/CMakeLists.txt create mode 100644 userspace/nslookup/main.cpp create mode 100644 userspace/resolver/CMakeLists.txt create mode 100644 userspace/resolver/main.cpp diff --git a/userspace/CMakeLists.txt b/userspace/CMakeLists.txt index 85dbb0fd..95091e98 100644 --- a/userspace/CMakeLists.txt +++ b/userspace/CMakeLists.txt @@ -11,14 +11,16 @@ set(USERSPACE_PROJECTS dhcp-client echo id - init image + init loadkeys ls meminfo mkdir mmap-shared-test + nslookup poweroff + resolver rm Shell sleep diff --git a/userspace/init/main.cpp b/userspace/init/main.cpp index 6e9a2aeb..5921bb00 100644 --- a/userspace/init/main.cpp +++ b/userspace/init/main.cpp @@ -36,6 +36,12 @@ int main() exit(1); } + if (fork() == 0) + { + execl("/bin/resolver", "resolver", NULL); + exit(1); + } + bool first = true; termios termios; diff --git a/userspace/nslookup/CMakeLists.txt b/userspace/nslookup/CMakeLists.txt new file mode 100644 index 00000000..1b706be8 --- /dev/null +++ b/userspace/nslookup/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.26) + +project(nslookup CXX) + +set(SOURCES + main.cpp +) + +add_executable(nslookup ${SOURCES}) +target_compile_options(nslookup PUBLIC -O2 -g) +target_link_libraries(nslookup PUBLIC libc) + +add_custom_target(nslookup-install + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/nslookup ${BANAN_BIN}/ + DEPENDS nslookup +) diff --git a/userspace/nslookup/main.cpp b/userspace/nslookup/main.cpp new file mode 100644 index 00000000..6943ca48 --- /dev/null +++ b/userspace/nslookup/main.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (argc != 2) + { + fprintf(stderr, "usage: %s DOMAIN\n", argv[0]); + return 1; + } + + int socket = ::socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (socket == -1) + { + perror("socket"); + return 1; + } + + sockaddr_un addr; + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, "/tmp/resolver.sock"); + if (connect(socket, (sockaddr*)&addr, sizeof(addr)) == -1) + { + perror("connect"); + return 1; + } + + if (send(socket, argv[1], strlen(argv[1]), 0) == -1) + { + perror("send"); + return 1; + } + + char buffer[128]; + ssize_t nrecv = recv(socket, buffer, sizeof(buffer), 0); + if (nrecv == -1) + { + perror("recv"); + return 1; + } + buffer[nrecv] = '\0'; + + printf("%s\n", buffer); + return 0; +} diff --git a/userspace/resolver/CMakeLists.txt b/userspace/resolver/CMakeLists.txt new file mode 100644 index 00000000..40b6b020 --- /dev/null +++ b/userspace/resolver/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.26) + +project(resolver CXX) + +set(SOURCES + main.cpp +) + +add_executable(resolver ${SOURCES}) +target_compile_options(resolver PUBLIC -O2 -g) +target_link_libraries(resolver PUBLIC libc ban) + +add_custom_target(resolver-install + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/resolver ${BANAN_BIN}/ + DEPENDS resolver +) diff --git a/userspace/resolver/main.cpp b/userspace/resolver/main.cpp new file mode 100644 index 00000000..7f2f80e9 --- /dev/null +++ b/userspace/resolver/main.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +struct DNSPacket +{ + BAN::NetworkEndian identification { 0 }; + BAN::NetworkEndian flags { 0 }; + BAN::NetworkEndian question_count { 0 }; + BAN::NetworkEndian answer_count { 0 }; + BAN::NetworkEndian authority_RR_count { 0 }; + BAN::NetworkEndian additional_RR_count { 0 }; + uint8_t data[]; +}; +static_assert(sizeof(DNSPacket) == 12); + +struct DNSAnswer +{ + uint8_t __storage[12]; + BAN::NetworkEndian& name() { return *reinterpret_cast*>(__storage + 0x00); }; + BAN::NetworkEndian& type() { return *reinterpret_cast*>(__storage + 0x02); }; + BAN::NetworkEndian& class_() { return *reinterpret_cast*>(__storage + 0x04); }; + BAN::NetworkEndian& ttl() { return *reinterpret_cast*>(__storage + 0x06); }; + BAN::NetworkEndian& data_len() { return *reinterpret_cast*>(__storage + 0x0A); }; + uint8_t data[]; +}; +static_assert(sizeof(DNSAnswer) == 12); + +bool send_dns_query(int socket, BAN::StringView domain, uint16_t id) +{ + static uint8_t buffer[4096]; + memset(buffer, 0, sizeof(buffer)); + + DNSPacket& request = *reinterpret_cast(buffer); + request.identification = id; + request.flags = 0x0100; + request.question_count = 1; + + size_t idx = 0; + + auto labels = MUST(BAN::StringView(domain).split('.')); + for (auto label : labels) + { + ASSERT(label.size() <= 0xFF); + request.data[idx++] = label.size(); + for (char c : label) + request.data[idx++] = c; + } + request.data[idx++] = 0x00; + + *(uint16_t*)&request.data[idx] = htons(0x01); idx += 2; + *(uint16_t*)&request.data[idx] = htons(0x01); idx += 2; + + sockaddr_in nameserver; + nameserver.sin_family = AF_INET; + nameserver.sin_port = htons(53); + nameserver.sin_addr.s_addr = inet_addr("8.8.8.8"); + if (sendto(socket, &request, sizeof(DNSPacket) + idx, 0, (sockaddr*)&nameserver, sizeof(nameserver)) == -1) + { + perror("sendto"); + return false; + } + + return true; +} + +BAN::Optional read_dns_response(int socket, uint16_t id) +{ + static uint8_t buffer[4096]; + + ssize_t nrecv = recvfrom(socket, buffer, sizeof(buffer), 0, nullptr, nullptr); + if (nrecv == -1) + { + perror("recvfrom"); + return {}; + } + + DNSPacket& reply = *reinterpret_cast(buffer); + if (reply.identification != id) + { + fprintf(stderr, "Reply to invalid packet\n"); + return {}; + } + if (reply.flags & 0x0F) + { + fprintf(stderr, "DNS error (rcode %u)\n", (unsigned)(reply.flags & 0xF)); + return {}; + } + + size_t idx = 0; + for (size_t i = 0; i < reply.question_count; i++) + { + while (reply.data[idx]) + idx += reply.data[idx] + 1; + idx += 5; + } + + DNSAnswer& answer = *reinterpret_cast(&reply.data[idx]); + if (answer.data_len() != 4) + { + fprintf(stderr, "Not IPv4\n"); + return {}; + } + + return inet_ntoa({ .s_addr = *reinterpret_cast(answer.data) }); +} + +int create_service_socket() +{ + int socket = ::socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (socket == -1) + { + perror("socket"); + return -1; + } + + sockaddr_un addr; + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, "/tmp/resolver.sock"); + if (bind(socket, (sockaddr*)&addr, sizeof(addr)) == -1) + { + perror("bind"); + close(socket); + return -1; + } + + if (chmod("/tmp/resolver.sock", 0777) == -1) + { + perror("chmod"); + close(socket); + return -1; + } + + if (listen(socket, 10) == -1) + { + perror("listen"); + close(socket); + return -1; + } + + return socket; +} + +BAN::Optional read_service_query(int socket) +{ + static char buffer[4096]; + ssize_t nrecv = recv(socket, buffer, sizeof(buffer), 0); + if (nrecv == -1) + { + perror("recv"); + return {}; + } + buffer[nrecv] = '\0'; + return BAN::String(buffer); +} + +int main(int, char**) +{ + srand(time(nullptr)); + + int service_socket = create_service_socket(); + if (service_socket == -1) + return 1; + + int dns_socket = socket(AF_INET, SOCK_DGRAM, 0); + if (dns_socket == -1) + { + perror("socket"); + return 1; + } + + for (;;) + { + int client = accept(service_socket, nullptr, nullptr); + if (client == -1) + { + perror("accept"); + continue; + } + + auto query = read_service_query(client); + if (!query.has_value()) + continue; + + uint16_t id = rand() % 0xFFFF; + + if (send_dns_query(dns_socket, *query, id)) + { + auto response = read_dns_response(dns_socket, id); + if (response.has_value()) + { + if (send(client, response->data(), response->size() + 1, 0) == -1) + perror("send"); + close(client); + continue; + } + } + + char message[] = "unavailable"; + send(client, message, sizeof(message), 0); + close(client); + } + + return 0; +}