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.
This commit is contained in:
parent
218456d127
commit
2424f38a62
|
@ -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
|
||||
|
|
|
@ -36,6 +36,12 @@ int main()
|
|||
exit(1);
|
||||
}
|
||||
|
||||
if (fork() == 0)
|
||||
{
|
||||
execl("/bin/resolver", "resolver", NULL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
|
||||
termios termios;
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -0,0 +1,47 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -0,0 +1,218 @@
|
|||
#include <BAN/ByteSpan.h>
|
||||
#include <BAN/Endianness.h>
|
||||
#include <BAN/HashMap.h>
|
||||
#include <BAN/IPv4.h>
|
||||
#include <BAN/String.h>
|
||||
#include <BAN/StringView.h>
|
||||
#include <BAN/Vector.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct DNSPacket
|
||||
{
|
||||
BAN::NetworkEndian<uint16_t> identification { 0 };
|
||||
BAN::NetworkEndian<uint16_t> flags { 0 };
|
||||
BAN::NetworkEndian<uint16_t> question_count { 0 };
|
||||
BAN::NetworkEndian<uint16_t> answer_count { 0 };
|
||||
BAN::NetworkEndian<uint16_t> authority_RR_count { 0 };
|
||||
BAN::NetworkEndian<uint16_t> additional_RR_count { 0 };
|
||||
uint8_t data[];
|
||||
};
|
||||
static_assert(sizeof(DNSPacket) == 12);
|
||||
|
||||
struct DNSAnswer
|
||||
{
|
||||
uint8_t __storage[12];
|
||||
BAN::NetworkEndian<uint16_t>& name() { return *reinterpret_cast<BAN::NetworkEndian<uint16_t>*>(__storage + 0x00); };
|
||||
BAN::NetworkEndian<uint16_t>& type() { return *reinterpret_cast<BAN::NetworkEndian<uint16_t>*>(__storage + 0x02); };
|
||||
BAN::NetworkEndian<uint16_t>& class_() { return *reinterpret_cast<BAN::NetworkEndian<uint16_t>*>(__storage + 0x04); };
|
||||
BAN::NetworkEndian<uint32_t>& ttl() { return *reinterpret_cast<BAN::NetworkEndian<uint32_t>*>(__storage + 0x06); };
|
||||
BAN::NetworkEndian<uint16_t>& data_len() { return *reinterpret_cast<BAN::NetworkEndian<uint16_t>*>(__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<DNSPacket*>(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<BAN::String> 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<DNSPacket*>(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<DNSAnswer*>(&reply.data[idx]);
|
||||
if (answer.data_len() != 4)
|
||||
{
|
||||
fprintf(stderr, "Not IPv4\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
return inet_ntoa({ .s_addr = *reinterpret_cast<uint32_t*>(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<BAN::String> 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;
|
||||
}
|
Loading…
Reference in New Issue