mirror of
https://github.com/neovim/neovim.git
synced 2025-09-14 07:18:17 +00:00
Server: use uv_getaddrinfo() for $NVIM_LISTEN_ADDRESS
This change implicitly adds IPv6 support. If the address contains ":", we try to use a TCP socket instead of a Unix domain socket. Everything in front of the last occurrence of ":" is the hostname and everything after it the port. If the hostname lookup fails, we fall back to using a Unix domain socket. If the port is empty ("localhost:"), a random port will be assigned. Examples: NVIM_LISTEN_ADDRESS=localhost:12345 -> TCP (IPv4 or IPv6), port: 12345 NVIM_LISTEN_ADDRESS=localhost: -> TCP (IPv4 or IPv6), port: random (> 1024) NVIM_LISTEN_ADDRESS=localhost:0 -> TCP (IPv4 or IPv6), port: random (> 1024) NVIM_LISTEN_ADDRESS=localhost -> Unix domain socket "localhost" in current dir
This commit is contained in:
@@ -14310,7 +14310,8 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|||||||
|
|
||||||
int result = server_start((char *) rettv->vval.v_string);
|
int result = server_start((char *) rettv->vval.v_string);
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
EMSG2("Failed to start server: %s", uv_strerror(result));
|
EMSG2("Failed to start server: %s",
|
||||||
|
result > 0 ? "Unknonwn system error" : uv_strerror(result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,63 +17,48 @@
|
|||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
#include "nvim/memory.h"
|
#include "nvim/memory.h"
|
||||||
#include "nvim/macros.h"
|
#include "nvim/macros.h"
|
||||||
|
#include "nvim/charset.h"
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "event/socket.c.generated.h"
|
# include "event/socket.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define NVIM_DEFAULT_TCP_PORT 7450
|
|
||||||
|
|
||||||
void socket_watcher_init(Loop *loop, SocketWatcher *watcher,
|
void socket_watcher_init(Loop *loop, SocketWatcher *watcher,
|
||||||
const char *endpoint, void *data)
|
const char *endpoint)
|
||||||
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3)
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
// Trim to `ADDRESS_MAX_SIZE`
|
xstrlcpy(watcher->addr, endpoint, sizeof(watcher->addr));
|
||||||
if (xstrlcpy(watcher->addr, endpoint, sizeof(watcher->addr))
|
char *addr = watcher->addr;
|
||||||
>= sizeof(watcher->addr)) {
|
char *host_end = strrchr(addr, ':');
|
||||||
// TODO(aktau): since this is not what the user wanted, perhaps we
|
|
||||||
// should return an error here
|
|
||||||
WLOG("Address was too long, truncated to %s", watcher->addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tcp = true;
|
if (host_end && addr != host_end) {
|
||||||
char ip[16], *ip_end = xstrchrnul(watcher->addr, ':');
|
intmax_t port;
|
||||||
|
int ret = getdigits_safe(&(char_u *){ (char_u *)host_end + 1 }, &port);
|
||||||
// (ip_end - addr) is always > 0, so convert to size_t
|
if (ret == FAIL || port < 0 || port > UINT16_MAX) {
|
||||||
size_t addr_len = (size_t)(ip_end - watcher->addr);
|
// Invalid port.
|
||||||
|
goto do_pipe;
|
||||||
if (addr_len > sizeof(ip) - 1) {
|
|
||||||
// Maximum length of an IPv4 address buffer is 15 (eg: 255.255.255.255)
|
|
||||||
addr_len = sizeof(ip) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the address part
|
|
||||||
xstrlcpy(ip, watcher->addr, addr_len + 1);
|
|
||||||
int port = NVIM_DEFAULT_TCP_PORT;
|
|
||||||
|
|
||||||
if (*ip_end == ':') {
|
|
||||||
// Extract the port
|
|
||||||
long lport = strtol(ip_end + 1, NULL, 10); // NOLINT
|
|
||||||
if (lport <= 0 || lport > 0xffff) {
|
|
||||||
// Invalid port, treat as named pipe or unix socket
|
|
||||||
tcp = false;
|
|
||||||
} else {
|
|
||||||
port = (int) lport;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (tcp) {
|
*host_end = '\0';
|
||||||
// Try to parse ip address
|
uv_getaddrinfo_t request;
|
||||||
if (uv_ip4_addr(ip, port, &watcher->uv.tcp.addr)) {
|
|
||||||
// Invalid address, treat as named pipe or unix socket
|
int retval = uv_getaddrinfo(&loop->uv, &request, NULL, addr, host_end+1,
|
||||||
tcp = false;
|
&(struct addrinfo){
|
||||||
|
.ai_family = AF_UNSPEC,
|
||||||
|
.ai_socktype = SOCK_STREAM,
|
||||||
|
});
|
||||||
|
*host_end = ':';
|
||||||
|
if (retval != 0) {
|
||||||
|
// Failed to look up address.
|
||||||
|
goto do_pipe;
|
||||||
}
|
}
|
||||||
}
|
*(host_end + 1) = '\0';
|
||||||
|
watcher->uv.tcp.addrinfo = request.addrinfo;
|
||||||
|
|
||||||
if (tcp) {
|
|
||||||
uv_tcp_init(&loop->uv, &watcher->uv.tcp.handle);
|
uv_tcp_init(&loop->uv, &watcher->uv.tcp.handle);
|
||||||
watcher->stream = STRUCT_CAST(uv_stream_t, &watcher->uv.tcp.handle);
|
watcher->stream = STRUCT_CAST(uv_stream_t, &watcher->uv.tcp.handle);
|
||||||
} else {
|
} else {
|
||||||
|
do_pipe:
|
||||||
uv_pipe_init(&loop->uv, &watcher->uv.pipe.handle, 0);
|
uv_pipe_init(&loop->uv, &watcher->uv.pipe.handle, 0);
|
||||||
watcher->stream = STRUCT_CAST(uv_stream_t, &watcher->uv.pipe.handle);
|
watcher->stream = STRUCT_CAST(uv_stream_t, &watcher->uv.pipe.handle);
|
||||||
}
|
}
|
||||||
@@ -82,33 +67,58 @@ void socket_watcher_init(Loop *loop, SocketWatcher *watcher,
|
|||||||
watcher->cb = NULL;
|
watcher->cb = NULL;
|
||||||
watcher->close_cb = NULL;
|
watcher->close_cb = NULL;
|
||||||
watcher->events = NULL;
|
watcher->events = NULL;
|
||||||
|
watcher->data = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
|
int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
watcher->cb = cb;
|
watcher->cb = cb;
|
||||||
int result;
|
int result = UV_EINVAL;
|
||||||
|
|
||||||
if (watcher->stream->type == UV_TCP) {
|
if (watcher->stream->type == UV_TCP) {
|
||||||
result = uv_tcp_bind(&watcher->uv.tcp.handle,
|
struct addrinfo *ai = watcher->uv.tcp.addrinfo;
|
||||||
(const struct sockaddr *)&watcher->uv.tcp.addr, 0);
|
|
||||||
|
for (; ai; ai = ai->ai_next) {
|
||||||
|
result = uv_tcp_bind(&watcher->uv.tcp.handle, ai->ai_addr, 0);
|
||||||
|
if (result != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result = uv_listen(watcher->stream, backlog, connection_cb);
|
||||||
|
if (result == 0) {
|
||||||
|
struct sockaddr_storage sas;
|
||||||
|
|
||||||
|
// When the endpoint in socket_watcher_init() didn't specify a port
|
||||||
|
// number, a free random port number will be assigned. sin_port will
|
||||||
|
// contain 0 in this case, unless uv_tcp_getsockname() is used first.
|
||||||
|
uv_tcp_getsockname(&watcher->uv.tcp.handle, (struct sockaddr *)&sas,
|
||||||
|
&(int){ sizeof(sas) });
|
||||||
|
uint16_t port = (sas.ss_family == AF_INET)
|
||||||
|
? ((struct sockaddr_in *)&sas)->sin_port
|
||||||
|
: ((struct sockaddr_in6 *)&sas)->sin6_port;
|
||||||
|
// v:servername uses the string from watcher->addr
|
||||||
|
size_t len = strlen(watcher->addr);
|
||||||
|
snprintf(watcher->addr+len, sizeof(watcher->addr)-len, "%" PRIu16,
|
||||||
|
ntohs(port));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uv_freeaddrinfo(watcher->uv.tcp.addrinfo);
|
||||||
} else {
|
} else {
|
||||||
result = uv_pipe_bind(&watcher->uv.pipe.handle, watcher->addr);
|
result = uv_pipe_bind(&watcher->uv.pipe.handle, watcher->addr);
|
||||||
}
|
if (result == 0) {
|
||||||
|
result = uv_listen(watcher->stream, backlog, connection_cb);
|
||||||
if (result == 0) {
|
}
|
||||||
result = uv_listen(watcher->stream, backlog, connection_cb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(result <= 0); // libuv should return negative error code or zero.
|
assert(result <= 0); // libuv should return negative error code or zero.
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
if (result == -EACCES) {
|
if (result == UV_EACCES) {
|
||||||
// Libuv converts ENOENT to EACCES for Windows compatibility, but if
|
// Libuv converts ENOENT to EACCES for Windows compatibility, but if
|
||||||
// the parent directory does not exist, ENOENT would be more accurate.
|
// the parent directory does not exist, ENOENT would be more accurate.
|
||||||
*path_tail((char_u *)watcher->addr) = NUL;
|
*path_tail((char_u *)watcher->addr) = NUL;
|
||||||
if (!os_path_exists((char_u *)watcher->addr)) {
|
if (!os_path_exists((char_u *)watcher->addr)) {
|
||||||
result = -ENOENT;
|
result = UV_ENOENT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@@ -20,7 +20,7 @@ struct socket_watcher {
|
|||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
uv_tcp_t handle;
|
uv_tcp_t handle;
|
||||||
struct sockaddr_in addr;
|
struct addrinfo *addrinfo;
|
||||||
} tcp;
|
} tcp;
|
||||||
struct {
|
struct {
|
||||||
uv_pipe_t handle;
|
uv_pipe_t handle;
|
||||||
|
@@ -97,31 +97,36 @@ char *server_address_new(void)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts listening for API calls on the TCP address or pipe path `endpoint`.
|
/// Starts listening for API calls.
|
||||||
/// The socket type is determined by parsing `endpoint`: If it's a valid IPv4
|
|
||||||
/// address in 'ip[:port]' format, then it will be TCP socket. The port is
|
|
||||||
/// optional and if omitted defaults to NVIM_DEFAULT_TCP_PORT. Otherwise it
|
|
||||||
/// will be a unix socket or named pipe.
|
|
||||||
///
|
///
|
||||||
/// @param endpoint Address of the server. Either a 'ip[:port]' string or an
|
/// The socket type is determined by parsing `endpoint`: If it's a valid IPv4
|
||||||
/// arbitrary identifier (trimmed to 256 bytes) for the unix socket or
|
/// or IPv6 address in 'ip:[port]' format, then it will be a TCP socket.
|
||||||
/// named pipe.
|
/// Otherwise it will be a Unix socket or named pipe (Windows).
|
||||||
|
///
|
||||||
|
/// If no port is given, a random one will be assigned.
|
||||||
|
///
|
||||||
|
/// @param endpoint Address of the server. Either a 'ip:[port]' string or an
|
||||||
|
/// arbitrary identifier (trimmed to 256 bytes) for the Unix
|
||||||
|
/// socket or named pipe.
|
||||||
/// @returns 0 on success, 1 on a regular error, and negative errno
|
/// @returns 0 on success, 1 on a regular error, and negative errno
|
||||||
/// on failure to bind or connect.
|
/// on failure to bind or listen.
|
||||||
int server_start(const char *endpoint)
|
int server_start(const char *endpoint)
|
||||||
{
|
{
|
||||||
if (endpoint == NULL) {
|
if (endpoint == NULL || endpoint[0] == '\0') {
|
||||||
ELOG("Attempting to start server on NULL endpoint");
|
ELOG("Empty or NULL endpoint");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketWatcher *watcher = xmalloc(sizeof(SocketWatcher));
|
SocketWatcher *watcher = xmalloc(sizeof(SocketWatcher));
|
||||||
socket_watcher_init(&main_loop, watcher, endpoint, NULL);
|
socket_watcher_init(&main_loop, watcher, endpoint);
|
||||||
|
|
||||||
// Check if a watcher for the endpoint already exists
|
// Check if a watcher for the endpoint already exists
|
||||||
for (int i = 0; i < watchers.ga_len; i++) {
|
for (int i = 0; i < watchers.ga_len; i++) {
|
||||||
if (!strcmp(watcher->addr, ((SocketWatcher **)watchers.ga_data)[i]->addr)) {
|
if (!strcmp(watcher->addr, ((SocketWatcher **)watchers.ga_data)[i]->addr)) {
|
||||||
ELOG("Already listening on %s", watcher->addr);
|
ELOG("Already listening on %s", watcher->addr);
|
||||||
|
if (watcher->stream->type == UV_TCP) {
|
||||||
|
uv_freeaddrinfo(watcher->uv.tcp.addrinfo);
|
||||||
|
}
|
||||||
socket_watcher_close(watcher, free_server);
|
socket_watcher_close(watcher, free_server);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user