9a9b91c940
It's finally done.. Signed-off-by: kaguya <vpshinomiya@protonmail.com>
549 lines
14 KiB
C++
549 lines
14 KiB
C++
#include <mlibc/lookup.hpp>
|
|
#include <mlibc/resolv_conf.hpp>
|
|
#include <mlibc/debug.hpp>
|
|
#include <mlibc/services.hpp>
|
|
#include <bits/ensure.h>
|
|
|
|
#include <frg/string.hpp>
|
|
#include <mlibc/allocator.hpp>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <arpa/inet.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
namespace mlibc {
|
|
|
|
namespace {
|
|
constexpr unsigned short RETURN_NOERROR [[maybe_unused]] = 0x0;
|
|
constexpr unsigned short RETURN_NXDOMAIN = 0x3;
|
|
|
|
constexpr unsigned int RECORD_A = 1;
|
|
constexpr unsigned int RECORD_CNAME = 5;
|
|
constexpr unsigned int RECORD_PTR = 12;
|
|
constexpr unsigned int RECORD_AAAA = 28;
|
|
} // namespace
|
|
|
|
static frg::string<MemoryAllocator> read_dns_name(char *buf, char *&it) {
|
|
frg::string<MemoryAllocator> res{getAllocator()};
|
|
while (true) {
|
|
char code = *it++;
|
|
if ((code & 0xC0) == 0xC0) {
|
|
// pointer
|
|
uint8_t offset = ((code & 0x3F) << 8) | *it++;
|
|
auto offset_it = buf + offset;
|
|
return res + read_dns_name(buf, offset_it);
|
|
} else if (!(code & 0xC0)) {
|
|
if (!code)
|
|
break;
|
|
|
|
for (int i = 0; i < code; i++)
|
|
res += (*it++);
|
|
|
|
if (*it)
|
|
res += '.';
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int lookup_name_dns(struct lookup_result &buf, const char *name,
|
|
frg::string<MemoryAllocator> &canon_name, int family) {
|
|
frg::string<MemoryAllocator> request{getAllocator()};
|
|
|
|
int num_q = 1;
|
|
struct dns_header header;
|
|
header.identification = htons(123);
|
|
header.flags = htons(0x100);
|
|
header.no_q = htons(num_q);
|
|
header.no_ans = htons(0);
|
|
header.no_auths = htons(0);
|
|
header.no_additional = htons(0);
|
|
|
|
request.resize(sizeof(header));
|
|
memcpy(request.data(), &header, sizeof(header));
|
|
|
|
const char *end = name;
|
|
while (*end != '\0') {
|
|
end = strchrnul(name, '.');
|
|
size_t length = end - name;
|
|
frg::string_view substring{name, length};
|
|
name += length + 1;
|
|
request += char(length);
|
|
request += substring;
|
|
}
|
|
|
|
request += char(0);
|
|
// set question type to fetch A or AAAA records
|
|
uint16_t qtype = RECORD_A;
|
|
if (family == AF_INET6)
|
|
qtype = RECORD_AAAA;
|
|
|
|
request += qtype >> 8;
|
|
request += qtype & 0xFF;
|
|
// set CLASS to IN
|
|
request += 0;
|
|
request += 1;
|
|
|
|
mlibc::service_result serv_buf{getAllocator()};
|
|
int serv_count = mlibc::lookup_serv_by_name(serv_buf, "domain", IPPROTO_UDP, SOCK_DGRAM, 0);
|
|
if (serv_count < 0) {
|
|
mlibc::infoLogger() << "mlibc: could not resolve DNS service" << frg::endlog;
|
|
return -EAI_SERVICE;
|
|
}
|
|
|
|
struct sockaddr_in sin = {};
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_port = htons(serv_buf[0].port);
|
|
|
|
auto nameserver = get_nameserver();
|
|
if (!inet_aton(nameserver ? nameserver->name.data() : "127.0.0.1", &sin.sin_addr)) {
|
|
mlibc::infoLogger() << "lookup_name_dns(): inet_aton() failed!" << frg::endlog;
|
|
return -EAI_SYSTEM;
|
|
}
|
|
|
|
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (fd < 0) {
|
|
mlibc::infoLogger() << "lookup_name_dns(): socket() failed" << frg::endlog;
|
|
return -EAI_SYSTEM;
|
|
}
|
|
|
|
size_t sent = sendto(fd, request.data(), request.size(), 0,
|
|
(struct sockaddr*)&sin, sizeof(sin));
|
|
if (sent != request.size()) {
|
|
mlibc::infoLogger() << "lookup_name_dns(): sendto() failed to send everything" << frg::endlog;
|
|
return -EAI_SYSTEM;
|
|
}
|
|
|
|
char response[256];
|
|
ssize_t rlen;
|
|
int num_ans = 0;
|
|
while ((rlen = recvfrom(fd, response, 256, 0, nullptr, nullptr)) >= 0) {
|
|
if ((size_t)rlen < sizeof(struct dns_header))
|
|
continue;
|
|
auto response_header = reinterpret_cast<struct dns_header*>(response);
|
|
if (response_header->identification != header.identification)
|
|
return -EAI_FAIL;
|
|
|
|
if ((ntohs(response_header->flags) & 0xF) == RETURN_NXDOMAIN)
|
|
return -EAI_NONAME;
|
|
|
|
auto it = response + sizeof(struct dns_header);
|
|
for (int i = 0; i < ntohs(response_header->no_q); i++) {
|
|
auto dns_name = read_dns_name(response, it);
|
|
(void) dns_name;
|
|
it += 4;
|
|
}
|
|
|
|
for (int i = 0; i < ntohs(response_header->no_ans); i++) {
|
|
struct dns_addr_buf buffer;
|
|
auto dns_name = read_dns_name(response, it);
|
|
|
|
uint16_t rr_type = (it[0] << 8) | it[1];
|
|
uint16_t rr_class = (it[2] << 8) | it[3];
|
|
uint16_t rr_length = (it[8] << 8) | it[9];
|
|
it += 10;
|
|
(void)rr_class;
|
|
|
|
switch (rr_type) {
|
|
case RECORD_A:
|
|
if (family != AF_UNSPEC && family != AF_INET)
|
|
continue;
|
|
|
|
memcpy(buffer.addr, it, rr_length);
|
|
it += rr_length;
|
|
buffer.family = AF_INET;
|
|
buffer.name = std::move(dns_name);
|
|
buf.buf.push(std::move(buffer));
|
|
break;
|
|
case RECORD_AAAA:
|
|
if (family != AF_UNSPEC && family != AF_INET6)
|
|
continue;
|
|
|
|
memcpy(buffer.addr, it, rr_length);
|
|
it += rr_length;
|
|
buffer.family = AF_INET6;
|
|
buffer.name = std::move(dns_name);
|
|
buf.buf.push(std::move(buffer));
|
|
break;
|
|
case RECORD_CNAME:
|
|
canon_name = read_dns_name(response, it);
|
|
buf.aliases.push(std::move(dns_name));
|
|
break;
|
|
default:
|
|
mlibc::infoLogger() << "lookup_name_dns: unknown rr type "
|
|
<< rr_type << frg::endlog;
|
|
break;
|
|
}
|
|
}
|
|
num_ans += ntohs(response_header->no_ans);
|
|
|
|
if (num_ans >= num_q)
|
|
break;
|
|
}
|
|
|
|
close(fd);
|
|
return buf.buf.size();
|
|
}
|
|
|
|
int lookup_addr_dns(frg::span<char> name, frg::array<uint8_t, 16> &addr, int family) {
|
|
frg::string<MemoryAllocator> request{getAllocator()};
|
|
|
|
int num_q = 1;
|
|
struct dns_header header;
|
|
header.identification = htons(123);
|
|
header.flags = htons(0x100);
|
|
header.no_q = htons(num_q);
|
|
header.no_ans = htons(0);
|
|
header.no_auths = htons(0);
|
|
header.no_additional = htons(0);
|
|
|
|
request.resize(sizeof(header));
|
|
memcpy(request.data(), &header, sizeof(header));
|
|
|
|
char addr_str[64];
|
|
if(!inet_ntop(family, addr.data(), addr_str, sizeof(addr_str))) {
|
|
switch(errno) {
|
|
case EAFNOSUPPORT:
|
|
return -EAI_FAMILY;
|
|
case ENOSPC:
|
|
return -EAI_OVERFLOW;
|
|
default:
|
|
return -EAI_FAIL;
|
|
}
|
|
}
|
|
frg::string<MemoryAllocator> req_str{getAllocator(), addr_str};
|
|
req_str += ".in-addr.arpa";
|
|
|
|
frg::string_view req_view{req_str.data(), req_str.size()};
|
|
size_t ptr = 0;
|
|
do {
|
|
size_t next = req_view.find_first('.', ptr);
|
|
size_t length = next != (size_t)-1 ? next - ptr : req_view.size() - ptr;
|
|
frg::string_view substring = req_view.sub_string(ptr, length);
|
|
request += char(length);
|
|
request += substring;
|
|
ptr = next + 1;
|
|
} while(ptr != 0);
|
|
|
|
request += char(0);
|
|
// set question type to fetch PTR records
|
|
request += 0;
|
|
request += 12;
|
|
// set CLASS to IN
|
|
request += 0;
|
|
request += 1;
|
|
|
|
mlibc::service_result serv_buf{getAllocator()};
|
|
int serv_count = mlibc::lookup_serv_by_name(serv_buf, "domain", IPPROTO_UDP, SOCK_DGRAM, 0);
|
|
if (serv_count < 0) {
|
|
mlibc::infoLogger() << "mlibc: could not resolve DNS service" << frg::endlog;
|
|
return -EAI_SERVICE;
|
|
}
|
|
|
|
struct sockaddr_in sin = {};
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_port = htons(serv_buf[0].port);
|
|
|
|
auto nameserver = get_nameserver();
|
|
if (!inet_aton(nameserver ? nameserver->name.data() : "127.0.0.1", &sin.sin_addr)) {
|
|
mlibc::infoLogger() << "lookup_name_dns(): inet_aton() failed!" << frg::endlog;
|
|
return -EAI_SYSTEM;
|
|
}
|
|
|
|
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (fd < 0) {
|
|
mlibc::infoLogger() << "lookup_name_dns(): socket() failed" << frg::endlog;
|
|
return -EAI_SYSTEM;
|
|
}
|
|
|
|
size_t sent = sendto(fd, request.data(), request.size(), 0,
|
|
(struct sockaddr*)&sin, sizeof(sin));
|
|
if (sent != request.size()) {
|
|
mlibc::infoLogger() << "lookup_name_dns(): sendto() failed to send everything" << frg::endlog;
|
|
return -EAI_SYSTEM;
|
|
}
|
|
|
|
char response[256];
|
|
ssize_t rlen;
|
|
int num_ans = 0;
|
|
while ((rlen = recvfrom(fd, response, 256, 0, nullptr, nullptr)) >= 0) {
|
|
if ((size_t)rlen < sizeof(struct dns_header))
|
|
continue;
|
|
auto response_header = reinterpret_cast<struct dns_header*>(response);
|
|
if (response_header->identification != header.identification)
|
|
return -EAI_FAIL;
|
|
|
|
auto it = response + sizeof(struct dns_header);
|
|
for (int i = 0; i < ntohs(response_header->no_q); i++) {
|
|
auto dns_name = read_dns_name(response, it);
|
|
(void) dns_name;
|
|
it += 4;
|
|
}
|
|
|
|
for (int i = 0; i < ntohs(response_header->no_ans); i++) {
|
|
struct dns_addr_buf buffer;
|
|
auto dns_name = read_dns_name(response, it);
|
|
|
|
uint16_t rr_type = (it[0] << 8) | it[1];
|
|
uint16_t rr_class = (it[2] << 8) | it[3];
|
|
uint16_t rr_length = (it[8] << 8) | it[9];
|
|
it += 10;
|
|
(void)rr_class;
|
|
(void)rr_length;
|
|
|
|
(void)dns_name;
|
|
|
|
switch (rr_type) {
|
|
case RECORD_PTR: {
|
|
auto ptr_name = read_dns_name(response, it);
|
|
if (ptr_name.size() >= name.size())
|
|
return -EAI_OVERFLOW;
|
|
std::copy(ptr_name.begin(), ptr_name.end(), name.data());
|
|
name.data()[ptr_name.size()] = '\0';
|
|
return 1;
|
|
}
|
|
default:
|
|
mlibc::infoLogger() << "lookup_addr_dns: unknown rr type "
|
|
<< rr_type << frg::endlog;
|
|
break;
|
|
}
|
|
num_ans += ntohs(response_header->no_ans);
|
|
|
|
if (num_ans >= num_q)
|
|
break;
|
|
}
|
|
}
|
|
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
int lookup_name_hosts(struct lookup_result &buf, const char *name,
|
|
frg::string<MemoryAllocator> &canon_name, int family) {
|
|
auto file = fopen("/etc/hosts", "r");
|
|
if (!file) {
|
|
switch (errno) {
|
|
case ENOENT:
|
|
case ENOTDIR:
|
|
case EACCES:
|
|
return -EAI_SERVICE;
|
|
default:
|
|
return -EAI_SYSTEM;
|
|
}
|
|
}
|
|
|
|
char line[128];
|
|
int name_length = strlen(name);
|
|
while (fgets(line, 128, file)) {
|
|
char *pos;
|
|
// same way to deal with comments as in services.cpp
|
|
if ((pos = strchr(line, '#'))) {
|
|
*pos++ = '\n';
|
|
*pos = '\0';
|
|
}
|
|
|
|
for(pos = line + 1; (pos = strstr(pos, name)) &&
|
|
(!isspace(pos[-1]) || !isspace(pos[name_length])); pos++);
|
|
if (!pos)
|
|
continue;
|
|
|
|
for (pos = line; !isspace(*pos); pos++);
|
|
*pos = '\0';
|
|
|
|
struct dns_addr_buf buffer;
|
|
|
|
if ((family == AF_UNSPEC || family == AF_INET) && inet_pton(AF_INET, line, buffer.addr)) {
|
|
buffer.family = AF_INET;
|
|
} else if((family == AF_UNSPEC || family == AF_INET6) && inet_pton(AF_INET6, line, buffer.addr)) {
|
|
buffer.family = AF_INET6;
|
|
} else {
|
|
continue; // not a valid address
|
|
}
|
|
|
|
pos++;
|
|
for(; *pos && isspace(*pos); pos++);
|
|
char *end;
|
|
for(end = pos; *end && !isspace(*end); end++);
|
|
|
|
buffer.name = frg::string<MemoryAllocator>{pos,
|
|
static_cast<size_t>(end - pos), getAllocator()};
|
|
canon_name = buffer.name;
|
|
|
|
buf.buf.push(std::move(buffer));
|
|
|
|
pos = end;
|
|
while (pos[1]) {
|
|
for (; *pos && isspace(*pos); pos++);
|
|
for (end = pos; *end && !isspace(*end); end++);
|
|
auto name = frg::string<MemoryAllocator>{pos,
|
|
static_cast<size_t>(end - pos), getAllocator()};
|
|
buf.aliases.push(std::move(name));
|
|
pos = end;
|
|
}
|
|
}
|
|
|
|
fclose(file);
|
|
return buf.buf.size();
|
|
}
|
|
|
|
int lookup_addr_hosts(frg::span<char> name, frg::array<uint8_t, 16> &addr, int family) {
|
|
auto file = fopen("/etc/hosts", "r");
|
|
if (!file) {
|
|
switch (errno) {
|
|
case ENOENT:
|
|
case ENOTDIR:
|
|
case EACCES:
|
|
return -EAI_SERVICE;
|
|
default:
|
|
return -EAI_SYSTEM;
|
|
}
|
|
}
|
|
|
|
// Buffer to hold ASCII version of address
|
|
char addr_str[64];
|
|
if(!inet_ntop(family, addr.data(), addr_str, sizeof(addr_str))) {
|
|
switch(errno) {
|
|
case EAFNOSUPPORT:
|
|
return -EAI_FAMILY;
|
|
case ENOSPC:
|
|
return -EAI_OVERFLOW;
|
|
default:
|
|
return -EAI_FAIL;
|
|
}
|
|
}
|
|
int addr_str_len = strlen(addr_str);
|
|
|
|
char line[128];
|
|
while (fgets(line, 128, file)) {
|
|
char *pos;
|
|
// same way to deal with comments as in services.cpp
|
|
if ((pos = strchr(line, '#'))) {
|
|
*pos++ = '\n';
|
|
*pos = '\0';
|
|
}
|
|
if (strncmp(line, addr_str, addr_str_len))
|
|
continue;
|
|
|
|
for (pos = line + addr_str_len + 1; isspace(*pos); pos++);
|
|
char *begin = pos;
|
|
for (; !isspace(*pos); pos++);
|
|
char *end = pos;
|
|
|
|
size_t size = end - begin;
|
|
if (size >= name.size())
|
|
return -EAI_OVERFLOW;
|
|
std::copy(begin, end, name.data());
|
|
name.data()[size] = '\0';
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int lookup_name_null(struct lookup_result &buf, int flags, int family) {
|
|
if (flags & AI_PASSIVE) {
|
|
if (family != AF_INET6) {
|
|
struct dns_addr_buf addr_buf;
|
|
addr_buf.family = AF_INET;
|
|
|
|
in_addr_t addr = INADDR_ANY;
|
|
memcpy(&addr_buf.addr, &addr, 4);
|
|
|
|
buf.buf.push_back(addr_buf);
|
|
}
|
|
if (family != AF_INET) {
|
|
struct dns_addr_buf addr_buf;
|
|
addr_buf.family = AF_INET6;
|
|
|
|
struct in6_addr addr = IN6ADDR_ANY_INIT;
|
|
memcpy(&addr_buf.addr, &addr, 16);
|
|
|
|
buf.buf.push_back(addr_buf);
|
|
}
|
|
} else {
|
|
if (family != AF_INET6) {
|
|
struct dns_addr_buf addr_buf;
|
|
addr_buf.family = AF_INET;
|
|
|
|
in_addr_t addr = INADDR_LOOPBACK;
|
|
memcpy(&addr_buf.addr, &addr, 4);
|
|
|
|
buf.buf.push_back(addr_buf);
|
|
}
|
|
if (family != AF_INET) {
|
|
struct dns_addr_buf addr_buf;
|
|
addr_buf.family = AF_INET6;
|
|
|
|
struct in6_addr addr = IN6ADDR_LOOPBACK_INIT;
|
|
memcpy(&addr_buf.addr, &addr, 16);
|
|
|
|
buf.buf.push_back(addr_buf);
|
|
}
|
|
}
|
|
return buf.buf.size();
|
|
}
|
|
|
|
int lookup_name_ip(struct lookup_result &buf, const char *name, int family) {
|
|
if (family == AF_INET) {
|
|
in_addr_t addr = 0;
|
|
int res = inet_pton(AF_INET, name, &addr);
|
|
|
|
if (res <= 0)
|
|
return -EAI_NONAME;
|
|
|
|
struct dns_addr_buf addr_buf;
|
|
addr_buf.family = AF_INET;
|
|
memcpy(&addr_buf.addr, &addr, 4);
|
|
|
|
buf.buf.push_back(addr_buf);
|
|
return 1;
|
|
}
|
|
|
|
if (family == AF_INET6) {
|
|
struct in6_addr addr{};
|
|
int res = inet_pton(AF_INET6, name, &addr);
|
|
|
|
if (res <= 0)
|
|
return -EAI_NONAME;
|
|
|
|
struct dns_addr_buf addr_buf;
|
|
addr_buf.family = AF_INET6;
|
|
memcpy(&addr_buf.addr, &addr, 16);
|
|
|
|
buf.buf.push_back(addr_buf);
|
|
return 1;
|
|
}
|
|
|
|
// If no family was specified we try ipv4 and then ipv6.
|
|
in_addr_t addr4 = 0;
|
|
int res = inet_pton(AF_INET, name, &addr4);
|
|
|
|
if (res > 0) {
|
|
struct dns_addr_buf addr_buf;
|
|
addr_buf.family = AF_INET;
|
|
memcpy(&addr_buf.addr, &addr4, 4);
|
|
|
|
buf.buf.push_back(addr_buf);
|
|
return 1;
|
|
}
|
|
|
|
struct in6_addr addr6{};
|
|
res = inet_pton(AF_INET6, name, &addr6);
|
|
|
|
if (res <= 0)
|
|
return -EAI_NONAME;
|
|
|
|
struct dns_addr_buf addr_buf;
|
|
addr_buf.family = AF_INET6;
|
|
memcpy(&addr_buf.addr, &addr6, 16);
|
|
|
|
buf.buf.push_back(addr_buf);
|
|
return 1;
|
|
}
|
|
|
|
} // namespace mlibc
|