ladybird/Userland/Libraries/LibC/netdb.cpp
sin-ack 62af6cd4f9 IPCCompiler: Remove hardcoded endpoint magic, attempt deux
This patch removes the IPC endpoint numbers that needed to be specified
in the IPC files.  Since the string hash is a (hopefully) collision free
number that depends on the name of the endpoint, we now use that
instead. :^)

Additionally, endpoint magic is now treated as a u32, because endpoint
numbers were never negative anyway.

For cases where the endpoint number does have to be hardcoded (a current
case is LookupServer because the endpoint number must be known in LibC),
the syntax has been made more explicit to avoid confusing those
unfamiliar.  To hardcode the endpoint magic, the following syntax is now
used:

endpoint EndpointName [magic=1234]
2021-04-25 14:06:56 +02:00

813 lines
23 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/ByteBuffer.h>
#include <AK/ScopeGuard.h>
#include <AK/String.h>
#include <Kernel/Net/IPv4.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
extern "C" {
int h_errno;
static hostent __gethostbyname_buffer;
static in_addr_t __gethostbyname_address;
static in_addr_t* __gethostbyname_address_list_buffer[2];
static hostent __gethostbyaddr_buffer;
static in_addr_t* __gethostbyaddr_address_list_buffer[2];
// XXX: IPCCompiler depends on LibC. Because of this, it cannot be compiled
// before LibC is. However, the lookup magic can only be obtained from the
// endpoint itself if IPCCompiler has compiled the IPC file, so this creates
// a chicken-and-egg situation. Because of this, the LookupServer endpoint magic
// is hardcoded here.
static constexpr i32 lookup_server_endpoint_magic = 9001;
// Get service entry buffers and file information for the getservent() family of functions.
static FILE* services_file = nullptr;
static const char* services_path = "/etc/services";
static bool fill_getserv_buffers(const char* line, ssize_t read);
static servent __getserv_buffer;
static String __getserv_name_buffer;
static String __getserv_protocol_buffer;
static int __getserv_port_buffer;
static Vector<ByteBuffer> __getserv_alias_list_buffer;
static Vector<char*> __getserv_alias_list;
static bool keep_service_file_open = false;
static ssize_t service_file_offset = 0;
// Get protocol entry buffers and file information for the getprotent() family of functions.
static FILE* protocols_file = nullptr;
static const char* protocols_path = "/etc/protocols";
static bool fill_getproto_buffers(const char* line, ssize_t read);
static protoent __getproto_buffer;
static String __getproto_name_buffer;
static Vector<ByteBuffer> __getproto_alias_list_buffer;
static Vector<char*> __getproto_alias_list;
static int __getproto_protocol_buffer;
static bool keep_protocols_file_open = false;
static ssize_t protocol_file_offset = 0;
static int connect_to_lookup_server()
{
int fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fd < 0) {
perror("socket");
return -1;
}
sockaddr_un address {
AF_LOCAL,
"/tmp/portal/lookup"
};
if (connect(fd, (const sockaddr*)&address, sizeof(address)) < 0) {
perror("connect_to_lookup_server");
close(fd);
return -1;
}
return fd;
}
static String gethostbyname_name_buffer;
hostent* gethostbyname(const char* name)
{
auto ipv4_address = IPv4Address::from_string(name);
if (ipv4_address.has_value()) {
gethostbyname_name_buffer = ipv4_address.value().to_string();
__gethostbyname_buffer.h_name = const_cast<char*>(gethostbyname_name_buffer.characters());
__gethostbyname_buffer.h_aliases = nullptr;
__gethostbyname_buffer.h_addrtype = AF_INET;
new (&__gethostbyname_address) IPv4Address(ipv4_address.value());
__gethostbyname_address_list_buffer[0] = &__gethostbyname_address;
__gethostbyname_address_list_buffer[1] = nullptr;
__gethostbyname_buffer.h_addr_list = (char**)__gethostbyname_address_list_buffer;
__gethostbyname_buffer.h_length = 4;
return &__gethostbyname_buffer;
}
int fd = connect_to_lookup_server();
if (fd < 0)
return nullptr;
auto close_fd_on_exit = ScopeGuard([fd] {
close(fd);
});
size_t name_length = strlen(name);
struct [[gnu::packed]] {
u32 message_size;
i32 endpoint_magic;
i32 message_id;
i32 name_length;
} request_header = {
(u32)(sizeof(request_header) - sizeof(request_header.message_size) + name_length),
lookup_server_endpoint_magic,
1,
(i32)name_length,
};
int nsent = write(fd, &request_header, sizeof(request_header));
if (nsent < 0) {
perror("write");
return nullptr;
}
VERIFY((size_t)nsent == sizeof(request_header));
nsent = write(fd, name, name_length);
if (nsent < 0) {
perror("write");
return nullptr;
}
VERIFY((size_t)nsent == name_length);
struct [[gnu::packed]] {
u32 message_size;
i32 endpoint_magic;
i32 message_id;
i32 code;
u64 addresses_count;
} response_header;
int nrecv = read(fd, &response_header, sizeof(response_header));
if (nrecv < 0) {
perror("recv");
return nullptr;
}
VERIFY((size_t)nrecv == sizeof(response_header));
if (response_header.endpoint_magic != lookup_server_endpoint_magic || response_header.message_id != 2) {
dbgln("Received an unexpected message");
return nullptr;
}
if (response_header.code != 0) {
// TODO: return a specific error.
return nullptr;
}
VERIFY(response_header.addresses_count > 0);
i32 response_length;
nrecv = read(fd, &response_length, sizeof(response_length));
if (nrecv < 0) {
perror("recv");
return nullptr;
}
VERIFY((size_t)nrecv == sizeof(response_length));
VERIFY(response_length == sizeof(__gethostbyname_address));
nrecv = read(fd, &__gethostbyname_address, response_length);
if (nrecv < 0) {
perror("recv");
return nullptr;
}
VERIFY(nrecv == response_length);
gethostbyname_name_buffer = name;
__gethostbyname_buffer.h_name = const_cast<char*>(gethostbyname_name_buffer.characters());
__gethostbyname_buffer.h_aliases = nullptr;
__gethostbyname_buffer.h_addrtype = AF_INET;
__gethostbyname_address_list_buffer[0] = &__gethostbyname_address;
__gethostbyname_address_list_buffer[1] = nullptr;
__gethostbyname_buffer.h_addr_list = (char**)__gethostbyname_address_list_buffer;
__gethostbyname_buffer.h_length = 4;
return &__gethostbyname_buffer;
}
static String gethostbyaddr_name_buffer;
hostent* gethostbyaddr(const void* addr, socklen_t addr_size, int type)
{
if (type != AF_INET) {
errno = EAFNOSUPPORT;
return nullptr;
}
if (addr_size < sizeof(in_addr)) {
errno = EINVAL;
return nullptr;
}
int fd = connect_to_lookup_server();
if (fd < 0)
return nullptr;
auto close_fd_on_exit = ScopeGuard([fd] {
close(fd);
});
const in_addr_t& in_addr = ((const struct in_addr*)addr)->s_addr;
struct [[gnu::packed]] {
u32 message_size;
i32 endpoint_magic;
i32 message_id;
i32 address_length;
} request_header = {
sizeof(request_header) - sizeof(request_header.message_size) + sizeof(in_addr),
lookup_server_endpoint_magic,
3,
(i32)sizeof(in_addr),
};
int nsent = write(fd, &request_header, sizeof(request_header));
if (nsent < 0) {
perror("write");
return nullptr;
}
VERIFY((size_t)nsent == sizeof(request_header));
nsent = write(fd, &in_addr, sizeof(in_addr));
if (nsent < 0) {
perror("write");
return nullptr;
}
VERIFY((size_t)nsent == sizeof(in_addr));
struct [[gnu::packed]] {
u32 message_size;
i32 endpoint_magic;
i32 message_id;
i32 code;
i32 name_length;
} response_header;
int nrecv = read(fd, &response_header, sizeof(response_header));
if (nrecv < 0) {
perror("recv");
return nullptr;
}
VERIFY((size_t)nrecv == sizeof(response_header));
if (response_header.endpoint_magic != lookup_server_endpoint_magic || response_header.message_id != 4) {
dbgln("Received an unexpected message");
return nullptr;
}
if (response_header.code != 0) {
// TODO: return a specific error.
return nullptr;
}
char* buffer;
auto string_impl = StringImpl::create_uninitialized(response_header.name_length, buffer);
nrecv = read(fd, buffer, response_header.name_length);
if (nrecv < 0) {
perror("recv");
return nullptr;
}
VERIFY(nrecv == response_header.name_length);
gethostbyaddr_name_buffer = move(string_impl);
__gethostbyaddr_buffer.h_name = buffer;
__gethostbyaddr_buffer.h_aliases = nullptr;
__gethostbyaddr_buffer.h_addrtype = AF_INET;
// FIXME: Should we populate the hostent's address list here with a sockaddr_in for the provided host?
__gethostbyaddr_address_list_buffer[0] = nullptr;
__gethostbyaddr_buffer.h_addr_list = (char**)__gethostbyaddr_address_list_buffer;
__gethostbyaddr_buffer.h_length = 4;
return &__gethostbyaddr_buffer;
}
struct servent* getservent()
{
//If the services file is not open, attempt to open it and return null if it fails.
if (!services_file) {
services_file = fopen(services_path, "r");
if (!services_file) {
perror("error opening services file");
return nullptr;
}
}
if (fseek(services_file, service_file_offset, SEEK_SET) != 0) {
perror("error seeking file");
fclose(services_file);
return nullptr;
}
char* line = nullptr;
size_t len = 0;
ssize_t read;
auto free_line_on_exit = ScopeGuard([line] {
if (line) {
free(line);
}
});
// Read lines from services file until an actual service name is found.
do {
read = getline(&line, &len, services_file);
service_file_offset += read;
if (read > 0 && (line[0] >= 65 && line[0] <= 122)) {
break;
}
} while (read != -1);
if (read == -1) {
fclose(services_file);
services_file = nullptr;
service_file_offset = 0;
return nullptr;
}
servent* service_entry = nullptr;
if (!fill_getserv_buffers(line, read))
return nullptr;
__getserv_buffer.s_name = const_cast<char*>(__getserv_name_buffer.characters());
__getserv_buffer.s_port = htons(__getserv_port_buffer);
__getserv_buffer.s_proto = const_cast<char*>(__getserv_protocol_buffer.characters());
__getserv_alias_list.clear_with_capacity();
__getserv_alias_list.ensure_capacity(__getserv_alias_list_buffer.size() + 1);
for (auto& alias : __getserv_alias_list_buffer)
__getserv_alias_list.unchecked_append(reinterpret_cast<char*>(alias.data()));
__getserv_alias_list.unchecked_append(nullptr);
__getserv_buffer.s_aliases = __getserv_alias_list.data();
service_entry = &__getserv_buffer;
if (!keep_service_file_open) {
endservent();
}
return service_entry;
}
struct servent* getservbyname(const char* name, const char* protocol)
{
if (name == nullptr)
return nullptr;
bool previous_file_open_setting = keep_service_file_open;
setservent(1);
struct servent* current_service = nullptr;
auto service_file_handler = ScopeGuard([previous_file_open_setting] {
if (!previous_file_open_setting) {
endservent();
}
});
while (true) {
current_service = getservent();
if (current_service == nullptr)
break;
else if (!protocol && strcmp(current_service->s_name, name) == 0)
break;
else if (strcmp(current_service->s_name, name) == 0 && strcmp(current_service->s_proto, protocol) == 0)
break;
}
return current_service;
}
struct servent* getservbyport(int port, const char* protocol)
{
bool previous_file_open_setting = keep_service_file_open;
setservent(1);
struct servent* current_service = nullptr;
auto service_file_handler = ScopeGuard([previous_file_open_setting] {
if (!previous_file_open_setting) {
endservent();
}
});
while (true) {
current_service = getservent();
if (current_service == nullptr)
break;
else if (!protocol && current_service->s_port == port)
break;
else if (current_service->s_port == port && (strcmp(current_service->s_proto, protocol) == 0))
break;
}
return current_service;
}
void setservent(int stay_open)
{
if (!services_file) {
services_file = fopen(services_path, "r");
if (!services_file) {
perror("error opening services file");
return;
}
}
rewind(services_file);
keep_service_file_open = stay_open;
service_file_offset = 0;
}
void endservent()
{
if (!services_file) {
return;
}
fclose(services_file);
services_file = nullptr;
}
// Fill the service entry buffer with the information contained
// in the currently read line, returns true if successful,
// false if failure occurs.
static bool fill_getserv_buffers(const char* line, ssize_t read)
{
//Splitting the line by tab delimiter and filling the servent buffers name, port, and protocol members.
String string_line = String(line, read);
string_line.replace(" ", "\t", true);
auto split_line = string_line.split('\t');
// This indicates an incorrect file format.
// Services file entries should always at least contain
// name and port/protocol, separated by tabs.
if (split_line.size() < 2) {
fprintf(stderr, "getservent(): malformed services file\n");
return false;
}
__getserv_name_buffer = split_line[0];
auto port_protocol_split = String(split_line[1]).split('/');
if (port_protocol_split.size() < 2) {
fprintf(stderr, "getservent(): malformed services file\n");
return false;
}
auto number = port_protocol_split[0].to_int();
if (!number.has_value())
return false;
__getserv_port_buffer = number.value();
// Remove any annoying whitespace at the end of the protocol.
port_protocol_split[1].replace(" ", "", true);
port_protocol_split[1].replace("\t", "", true);
port_protocol_split[1].replace("\n", "", true);
__getserv_protocol_buffer = port_protocol_split[1];
__getserv_alias_list_buffer.clear();
// If there are aliases for the service, we will fill the alias list buffer.
if (split_line.size() > 2 && !split_line[2].starts_with('#')) {
for (size_t i = 2; i < split_line.size(); i++) {
if (split_line[i].starts_with('#')) {
break;
}
auto alias = split_line[i].to_byte_buffer();
alias.append("\0", sizeof(char));
__getserv_alias_list_buffer.append(alias);
}
}
return true;
}
struct protoent* getprotoent()
{
// If protocols file isn't open, attempt to open and return null on failure.
if (!protocols_file) {
protocols_file = fopen(protocols_path, "r");
if (!protocols_file) {
perror("error opening protocols file");
return nullptr;
}
}
if (fseek(protocols_file, protocol_file_offset, SEEK_SET) != 0) {
perror("error seeking protocols file");
fclose(protocols_file);
return nullptr;
}
char* line = nullptr;
size_t len = 0;
ssize_t read;
auto free_line_on_exit = ScopeGuard([line] {
if (line) {
free(line);
}
});
do {
read = getline(&line, &len, protocols_file);
protocol_file_offset += read;
if (read > 0 && (line[0] >= 65 && line[0] <= 122)) {
break;
}
} while (read != -1);
if (read == -1) {
fclose(protocols_file);
protocols_file = nullptr;
protocol_file_offset = 0;
return nullptr;
}
struct protoent* protocol_entry = nullptr;
if (!fill_getproto_buffers(line, read))
return nullptr;
__getproto_buffer.p_name = const_cast<char*>(__getproto_name_buffer.characters());
__getproto_buffer.p_proto = __getproto_protocol_buffer;
__getproto_alias_list.clear_with_capacity();
__getproto_alias_list.ensure_capacity(__getproto_alias_list_buffer.size() + 1);
for (auto& alias : __getproto_alias_list_buffer)
__getproto_alias_list.unchecked_append(reinterpret_cast<char*>(alias.data()));
__getserv_alias_list.unchecked_append(nullptr);
__getproto_buffer.p_aliases = __getproto_alias_list.data();
protocol_entry = &__getproto_buffer;
if (!keep_protocols_file_open)
endprotoent();
return protocol_entry;
}
struct protoent* getprotobyname(const char* name)
{
bool previous_file_open_setting = keep_protocols_file_open;
setprotoent(1);
struct protoent* current_protocol = nullptr;
auto protocol_file_handler = ScopeGuard([previous_file_open_setting] {
if (!previous_file_open_setting) {
endprotoent();
}
});
while (true) {
current_protocol = getprotoent();
if (current_protocol == nullptr)
break;
else if (strcmp(current_protocol->p_name, name) == 0)
break;
}
return current_protocol;
}
struct protoent* getprotobynumber(int proto)
{
bool previous_file_open_setting = keep_protocols_file_open;
setprotoent(1);
struct protoent* current_protocol = nullptr;
auto protocol_file_handler = ScopeGuard([previous_file_open_setting] {
if (!previous_file_open_setting) {
endprotoent();
}
});
while (true) {
current_protocol = getprotoent();
if (current_protocol == nullptr)
break;
else if (current_protocol->p_proto == proto)
break;
}
return current_protocol;
}
void setprotoent(int stay_open)
{
if (!protocols_file) {
protocols_file = fopen(protocols_path, "r");
if (!protocols_file) {
perror("setprotoent(): error opening protocols file");
return;
}
}
rewind(protocols_file);
keep_protocols_file_open = stay_open;
protocol_file_offset = 0;
}
void endprotoent()
{
if (!protocols_file) {
return;
}
fclose(protocols_file);
protocols_file = nullptr;
}
static bool fill_getproto_buffers(const char* line, ssize_t read)
{
String string_line = String(line, read);
string_line.replace(" ", "\t", true);
auto split_line = string_line.split('\t');
// This indicates an incorrect file format. Protocols file entries should
// always have at least a name and a protocol.
if (split_line.size() < 2) {
fprintf(stderr, "getprotoent(): malformed protocols file\n");
return false;
}
__getproto_name_buffer = split_line[0];
auto number = split_line[1].to_int();
if (!number.has_value())
return false;
__getproto_protocol_buffer = number.value();
__getproto_alias_list_buffer.clear();
// If there are aliases for the protocol, we will fill the alias list buffer.
if (split_line.size() > 2 && !split_line[2].starts_with('#')) {
for (size_t i = 2; i < split_line.size(); i++) {
if (split_line[i].starts_with('#'))
break;
auto alias = split_line[i].to_byte_buffer();
alias.append("\0", sizeof(char));
__getproto_alias_list_buffer.append(alias);
}
}
return true;
}
int getaddrinfo(const char* __restrict node, const char* __restrict service, const struct addrinfo* __restrict hints, struct addrinfo** __restrict res)
{
dbgln("getaddrinfo: node={}, service={}, hints->ai_family={}", (const char*)node, (const char*)service, hints ? hints->ai_family : 0);
*res = nullptr;
if (hints && hints->ai_family != AF_INET && hints->ai_family != AF_UNSPEC)
return EAI_FAMILY;
if (!node) {
if (hints && hints->ai_flags & AI_PASSIVE)
node = "0.0.0.0";
else
node = "127.0.0.1";
}
auto host_ent = gethostbyname(node);
if (!host_ent)
return EAI_FAIL;
const char* proto = nullptr;
if (hints && hints->ai_socktype) {
switch (hints->ai_socktype) {
case SOCK_STREAM:
proto = "tcp";
break;
case SOCK_DGRAM:
proto = "udp";
break;
default:
return EAI_SOCKTYPE;
}
}
long port;
int socktype;
servent* svc_ent = nullptr;
if (!hints || (hints->ai_flags & AI_NUMERICSERV) == 0) {
svc_ent = getservbyname(service, proto);
}
if (!svc_ent) {
if (service) {
char* end;
port = htons(strtol(service, &end, 10));
if (*end)
return EAI_FAIL;
} else {
port = htons(0);
}
if (hints && hints->ai_socktype != 0)
socktype = hints->ai_socktype;
else
socktype = SOCK_STREAM;
} else {
port = svc_ent->s_port;
socktype = strcmp(svc_ent->s_proto, "tcp") ? SOCK_STREAM : SOCK_DGRAM;
}
addrinfo* first_info = nullptr;
addrinfo* prev_info = nullptr;
for (int host_index = 0; host_ent->h_addr_list[host_index]; host_index++) {
sockaddr_in* sin = new sockaddr_in;
sin->sin_family = AF_INET;
sin->sin_port = port;
memcpy(&sin->sin_addr.s_addr, host_ent->h_addr_list[host_index], host_ent->h_length);
addrinfo* info = new addrinfo;
info->ai_flags = 0;
info->ai_family = AF_INET;
info->ai_socktype = socktype;
info->ai_protocol = PF_INET;
info->ai_addrlen = sizeof(*sin);
info->ai_addr = reinterpret_cast<sockaddr*>(sin);
if (hints && hints->ai_flags & AI_CANONNAME)
info->ai_canonname = strdup(host_ent->h_name);
else
info->ai_canonname = nullptr;
info->ai_next = nullptr;
if (!first_info)
first_info = info;
if (prev_info)
prev_info->ai_next = info;
prev_info = info;
}
if (first_info) {
*res = first_info;
return 0;
} else
return EAI_NONAME;
}
void freeaddrinfo(struct addrinfo* res)
{
if (res) {
delete reinterpret_cast<sockaddr_in*>(res->ai_addr);
free(res->ai_canonname);
freeaddrinfo(res->ai_next);
delete res;
}
}
const char* gai_strerror(int errcode)
{
switch (errcode) {
case EAI_ADDRFAMILY:
return "no address for this address family available";
case EAI_AGAIN:
return "name server returned temporary failure";
case EAI_BADFLAGS:
return "invalid flags";
case EAI_FAIL:
return "name server returned permanent failure";
case EAI_FAMILY:
return "unsupported address family";
case EAI_MEMORY:
return "out of memory";
case EAI_NODATA:
return "no address available";
case EAI_NONAME:
return "node or service is not known";
case EAI_SERVICE:
return "service not available";
case EAI_SOCKTYPE:
return "unsupported socket type";
case EAI_SYSTEM:
return "system error";
case EAI_OVERFLOW:
return "buffer too small";
default:
return "invalid error code";
}
}
int getnameinfo(const struct sockaddr* __restrict addr, socklen_t addrlen, char* __restrict host, socklen_t hostlen, char* __restrict serv, socklen_t servlen, int flags)
{
if (addr->sa_family != AF_INET || addrlen < sizeof(sockaddr_in))
return EAI_FAMILY;
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(addr);
if (host && hostlen > 0) {
if (flags & NI_NAMEREQD)
dbgln("getnameinfo flag NI_NAMEREQD not implemented");
if (!inet_ntop(AF_INET, &sin->sin_addr, host, hostlen)) {
if (errno == ENOSPC)
return EAI_OVERFLOW;
else
return EAI_SYSTEM;
}
}
if (serv && servlen > 0) {
if (snprintf(serv, servlen, "%d", (int)ntohs(sin->sin_port)) > (int)servlen)
return EAI_OVERFLOW;
}
return 0;
}
}