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; +}