mirror of
https://git.proxmox.com/git/wasi-libc
synced 2025-07-09 12:19:12 +00:00

Hello, While experimenting with the `wasm32-wasip2` target and CPython, I discovered an issue with the `getaddrinfo()` implementation: it fails to resolve the provided service into a port number, causing `sin_port` to always be set to 0. This issue leads to failures in network-related functions that rely on `getaddrinfo()`, such as Python's `urllib3` library, which passes the result directly to `connect()`. This results in connection attempts using a port value of 0, which naturally fails. ### Minimal example to reproduce the problem ```c #include <arpa/inet.h> #include <netdb.h> #include <stdio.h> int main(void) { struct addrinfo *res = NULL; getaddrinfo("google.com", "443", NULL, &res); for (struct addrinfo *i = res; i != NULL; i = i->ai_next) { char str[INET6_ADDRSTRLEN]; if (i->ai_addr->sa_family == AF_INET) { struct sockaddr_in *p = (struct sockaddr_in *)i->ai_addr; int port = ntohs(p->sin_port); printf("%s: %i\n", inet_ntop(AF_INET, &p->sin_addr, str, sizeof(str)), port); } else if (i->ai_addr->sa_family == AF_INET6) { struct sockaddr_in6 *p = (struct sockaddr_in6 *)i->ai_addr; int port = ntohs(p->sin6_port); printf("%s: %i\n", inet_ntop(AF_INET6, &p->sin6_addr, str, sizeof(str)), port); } } return 0; } ``` ``` $ /opt/wasi-sdk/bin/clang -target wasm32-wasip2 -o foo foo.c $ wasmtime run -S allow-ip-name-lookup=y foo 216.58.211.238: 0 2a00:1450:4026:808::200e: 0 ``` Expected output: ``` 216.58.211.238: 443 2a00:1450:4026:808::200e: 443 ``` ### Root Cause The root cause is that `getaddrinfo()` does not correctly translate the provided service into a port number. As described in the `getaddrinfo()` man [page](https://man7.org/linux/man-pages/man3/getaddrinfo.3.html), the function should: > service sets the port in each returned address structure. If this argument is a service name (see [services(5)](https://man7.org/linux/man-pages/man5/services.5.html)), it is translated to the corresponding port number. This argument can also be specified as a decimal number, which is simply converted to binary. If service is NULL, then the port number of the returned socket addresses will be left uninitialized. ### Proposed Fix This pull request addresses the issue by implementing the following behavior for `getaddrinfo()`: * If the service is `NULL`, the port number in the returned socket addresses remains uninitialized. * The value is converted to an integer and validated if the service is numeric. The PR does not currently add support for translating named services into port numbers because `getservbyname()` has not been implemented. In cases where a named service is provided, the `EAI_NONAME` error code is returned.
254 lines
5.6 KiB
C
254 lines
5.6 KiB
C
#include <netdb.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <wasi/sockets_utils.h>
|
|
|
|
_Thread_local int h_errno = 0;
|
|
|
|
static int map_error(ip_name_lookup_error_code_t error)
|
|
{
|
|
switch (error) {
|
|
case NETWORK_ERROR_CODE_OUT_OF_MEMORY:
|
|
return EAI_MEMORY;
|
|
case NETWORK_ERROR_CODE_NAME_UNRESOLVABLE:
|
|
return EAI_NONAME;
|
|
case NETWORK_ERROR_CODE_TEMPORARY_RESOLVER_FAILURE:
|
|
return EAI_AGAIN;
|
|
case NETWORK_ERROR_CODE_PERMANENT_RESOLVER_FAILURE:
|
|
return EAI_FAIL;
|
|
|
|
default:
|
|
errno = __wasi_sockets_utils__map_error(error);
|
|
return EAI_SYSTEM;
|
|
}
|
|
}
|
|
|
|
static int add_addr(ip_name_lookup_option_ip_address_t address,
|
|
in_port_t port,
|
|
const struct addrinfo *restrict hint,
|
|
struct addrinfo **restrict current,
|
|
struct addrinfo **restrict res)
|
|
{
|
|
int family;
|
|
struct sockaddr *addr;
|
|
socklen_t addrlen;
|
|
switch (address.val.tag) {
|
|
case NETWORK_IP_ADDRESS_IPV4: {
|
|
if (hint && hint->ai_family != AF_UNSPEC &&
|
|
hint->ai_family != AF_INET) {
|
|
return 0;
|
|
}
|
|
|
|
network_ipv4_address_t ip = address.val.val.ipv4;
|
|
|
|
family = PF_INET;
|
|
addrlen = sizeof(struct sockaddr_in);
|
|
addr = malloc(addrlen);
|
|
if (addr == NULL) {
|
|
freeaddrinfo(*res);
|
|
return EAI_MEMORY;
|
|
}
|
|
|
|
struct sockaddr_in sockaddr = {
|
|
.sin_family = AF_INET,
|
|
.sin_port = port,
|
|
.sin_addr = { .s_addr = ip.f0 | (ip.f1 << 8) |
|
|
(ip.f2 << 16) | (ip.f3 << 24) },
|
|
};
|
|
memcpy(addr, &sockaddr, addrlen);
|
|
break;
|
|
}
|
|
case NETWORK_IP_ADDRESS_IPV6: {
|
|
if (hint && hint->ai_family != AF_UNSPEC &&
|
|
hint->ai_family != AF_INET6) {
|
|
return 0;
|
|
}
|
|
|
|
network_ipv6_address_t ip = address.val.val.ipv6;
|
|
|
|
family = PF_INET6;
|
|
addrlen = sizeof(struct sockaddr_in6);
|
|
addr = malloc(addrlen);
|
|
if (addr == NULL) {
|
|
freeaddrinfo(*res);
|
|
return EAI_MEMORY;
|
|
}
|
|
|
|
struct sockaddr_in6 sockaddr = {
|
|
.sin6_family = AF_INET6,
|
|
.sin6_port = port,
|
|
.sin6_addr = {
|
|
.s6_addr = {
|
|
ip.f0 >> 8,
|
|
ip.f0 & 0xFF,
|
|
ip.f1 >> 8,
|
|
ip.f1 & 0xFF,
|
|
ip.f2 >> 8,
|
|
ip.f2 & 0xFF,
|
|
ip.f3 >> 8,
|
|
ip.f3 & 0xFF,
|
|
ip.f4 >> 8,
|
|
ip.f4 & 0xFF,
|
|
ip.f5 >> 8,
|
|
ip.f5 & 0xFF,
|
|
ip.f6 >> 8,
|
|
ip.f6 & 0xFF,
|
|
ip.f7 >> 8,
|
|
ip.f7 & 0xFF,
|
|
} },
|
|
.sin6_flowinfo = 0,
|
|
.sin6_scope_id = 0,
|
|
};
|
|
memcpy(addr, &sockaddr, addrlen);
|
|
break;
|
|
}
|
|
default: /* unreachable */
|
|
abort();
|
|
}
|
|
|
|
struct addrinfo *result = malloc(sizeof(struct addrinfo));
|
|
if (result == NULL) {
|
|
freeaddrinfo(*res);
|
|
return EAI_MEMORY;
|
|
}
|
|
|
|
*result = (struct addrinfo){
|
|
.ai_family = family,
|
|
.ai_flags = 0,
|
|
.ai_socktype = SOCK_STREAM,
|
|
.ai_protocol = 0,
|
|
.ai_addrlen = addrlen,
|
|
.ai_addr = addr,
|
|
.ai_canonname = NULL,
|
|
.ai_next = NULL,
|
|
};
|
|
|
|
if (*current) {
|
|
(*current)->ai_next = result;
|
|
*current = result;
|
|
} else {
|
|
*current = result;
|
|
*res = result;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int getaddrinfo(const char *restrict host, const char *restrict serv,
|
|
const struct addrinfo *restrict hint,
|
|
struct addrinfo **restrict res)
|
|
{
|
|
if (host == NULL) {
|
|
host = "localhost";
|
|
}
|
|
|
|
*res = NULL;
|
|
struct addrinfo *current = NULL;
|
|
wasip2_string_t name = { .ptr = (uint8_t *)host, .len = strlen(host) };
|
|
ip_name_lookup_own_resolve_address_stream_t stream;
|
|
ip_name_lookup_error_code_t error;
|
|
if (ip_name_lookup_resolve_addresses(
|
|
__wasi_sockets_utils__borrow_network(), &name, &stream,
|
|
&error)) {
|
|
ip_name_lookup_borrow_resolve_address_stream_t stream_borrow =
|
|
ip_name_lookup_borrow_resolve_address_stream(stream);
|
|
// The 'serv' parameter can be either a port number or a service name.
|
|
//
|
|
// TODO wasi-sockets: If the conversion of 'serv' to a valid port
|
|
// number fails, use getservbyname() to resolve the service name to
|
|
// its corresponding port number. This can be done after the
|
|
// getservbyname function is implemented.)
|
|
int port = 0;
|
|
if (serv != NULL) {
|
|
port = __wasi_sockets_utils__parse_port(serv);
|
|
if (port < 0) {
|
|
return EAI_NONAME;
|
|
}
|
|
}
|
|
while (true) {
|
|
ip_name_lookup_option_ip_address_t address;
|
|
if (ip_name_lookup_method_resolve_address_stream_resolve_next_address(
|
|
stream_borrow, &address, &error)) {
|
|
if (address.is_some) {
|
|
int error = add_addr(address, htons(port), hint,
|
|
¤t, res);
|
|
if (error) {
|
|
return error;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else if (error == NETWORK_ERROR_CODE_WOULD_BLOCK) {
|
|
ip_name_lookup_own_pollable_t pollable =
|
|
ip_name_lookup_method_resolve_address_stream_subscribe(
|
|
stream_borrow);
|
|
poll_borrow_pollable_t pollable_borrow =
|
|
poll_borrow_pollable(pollable);
|
|
poll_method_pollable_block(pollable_borrow);
|
|
poll_pollable_drop_own(pollable);
|
|
} else {
|
|
freeaddrinfo(*res);
|
|
return map_error(error);
|
|
}
|
|
}
|
|
} else {
|
|
return map_error(error);
|
|
}
|
|
}
|
|
|
|
void freeaddrinfo(struct addrinfo *p)
|
|
{
|
|
while (p) {
|
|
struct addrinfo *next = p->ai_next;
|
|
free(p->ai_addr);
|
|
free(p);
|
|
p = next;
|
|
}
|
|
}
|
|
|
|
int getnameinfo(const struct sockaddr *restrict sa, socklen_t salen,
|
|
char *restrict host, socklen_t hostlen, char *restrict serv,
|
|
socklen_t servlen, int flags)
|
|
{
|
|
// TODO wasi-sockets
|
|
abort();
|
|
}
|
|
|
|
struct hostent *gethostbyname(const char *name)
|
|
{
|
|
// TODO wasi-sockets
|
|
return NULL;
|
|
}
|
|
|
|
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type)
|
|
{
|
|
// TODO wasi-sockets
|
|
return NULL;
|
|
}
|
|
|
|
const char *hstrerror(int err)
|
|
{
|
|
// TODO wasi-sockets
|
|
return "hstrerror: TODO";
|
|
}
|
|
|
|
struct servent *getservbyname(const char *name, const char *proto)
|
|
{
|
|
// TODO wasi-sockets
|
|
return NULL;
|
|
}
|
|
|
|
struct servent *getservbyport(int port, const char *proto)
|
|
{
|
|
// TODO wasi-sockets
|
|
return NULL;
|
|
}
|
|
|
|
struct protoent *getprotobyname(const char *name)
|
|
{
|
|
// TODO wasi-sockets
|
|
return NULL;
|
|
}
|