-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
[RDY] Use uv_getaddrinfo() for servers #6680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Should we be using |
I look into it! |
See function |
src/nvim/event/socket.c
Outdated
for (; ai; ai = ai->ai_next) { | ||
memcpy(&watcher->uv.tcp.addr, ai->ai_addr, ai->ai_addrlen); | ||
result = uv_tcp_bind(&watcher->uv.tcp.handle, | ||
(const struct sockaddr *)&watcher->uv.tcp.addr, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ai->ai_addr
can be used here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I guess it would be more explicit to use watcher
since it's also used for all the following calls.. but using ai->ai_addr
I can avoid the cast and it just looks better.
👍
@@ -90,14 +71,38 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb) | |||
int result; | |||
|
|||
if (watcher->stream->type == UV_TCP) { | |||
result = uv_tcp_bind(&watcher->uv.tcp.handle, | |||
(const struct sockaddr *)&watcher->uv.tcp.addr, 0); | |||
struct addrinfo *ai = watcher->uv.tcp.addrinfo; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If ai
is NULL
then result
is not initialized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops. I'll fix that.
.oO(Actually it shouldn't happen unless getaddrinfo()
can return 0 and the resulting pointer to the addrinfo
structures is NULL nevertheless.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since libuv is expected to return negative integers, I now initialise it with -EINVAL
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mhinz This should be UV_EINVAL without -
, not -EINVAL
. According to C99 only EDOM, EILSEQ and ERANGE must be defined, no EINVAL in this list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also on Windows UV_EINVAL and -EINVAL are going to be different AFAIK. And you return UV errors in other places. Also UV_EACCESS and not -EACCESS
below, UV_ENOENT and not -ENOENT
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/nvim/event/socket.c
Outdated
&(int){sizeof(struct sockaddr_storage)}); | ||
uint16_t port = (ai->ai_family == AF_INET) | ||
? ((struct sockaddr_in *)&watcher->uv.tcp.addr)->sin_port | ||
: ((struct sockaddr_in6 *)&watcher->uv.tcp.addr)->sin6_port; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These numbers are encoded in network byte order, what we want is host byte order, so we need to call ntohs()
on sin_port/sin6_port
.
ntohs()
is called later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ntohs()
is used when port
is used. I wanted to avoid wrapping this already huge assignment and using an extra assignment à la port = ntohs(port)
.
src/nvim/event/socket.c
Outdated
// 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 *)&watcher->uv.tcp.addr, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use a local struct sockaddr_storage x
here? The content of x
is not used in this call. Member watcher->uv.tcp.addr
could be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/nvim/event/socket.c
Outdated
: ((struct sockaddr_in6 *)&watcher->uv.tcp.addr)->sin6_port; | ||
// v:servername uses the string from watcher->addr | ||
snprintf(watcher->addr, sizeof(watcher->addr), "%s:%" PRIu16, | ||
watcher->addr, ntohs(port)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hostname is not showing for me. Could be that we reading and writing from the same buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Okay, I'll make a copy of watcher->addr
first.
src/nvim/event/socket.c
Outdated
struct addrinfo *ai = watcher->uv.tcp.addrinfo; | ||
|
||
for (; ai; ai = ai->ai_next) { | ||
memcpy(&watcher->uv.tcp.addr, ai->ai_addr, ai->ai_addrlen); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be removed if we use a local variable, see below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/nvim/event/socket.c
Outdated
struct addrinfo *ai = watcher->uv.tcp.addrinfo; | ||
|
||
for (; ai; ai = ai->ai_next) { | ||
struct sockaddr_storage sas = *(struct sockaddr_storage *)ai->ai_addr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be moved into the if (result == 0) {...}
block without initialization (uv_tcp_getsockname()
will fill it). Dereferencing ai->ai_addr
can go wrong if the structure is smaller than struct sockaddr_storage
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dereferencing ai->ai_addr can go wrong if the structure is smaller than struct sockaddr_storage.
How would I work around that? A union
with sockaddr_in
and sockaddr_in6
and switching on ai_family
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just put struct sockaddr_storage sas;
before uv_tcp_getsockname()
. We get the socket name from the socket descriptor, not from the content of sas
.
src/nvim/event/socket.c
Outdated
// v:servername uses the string from watcher->addr | ||
char addr[sizeof(watcher->addr)]; | ||
strcpy(addr, watcher->addr); | ||
snprintf(watcher->addr, sizeof(addr), "%s:%" PRIu16, addr, ntohs(port)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could also be done with a strlen()
:
size_t len = strlen(watcher->addr);
snprintf(watcher->addr +len, sizeof(watcher->addr)-len, ":%" PRIu16, ntohs(port));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/nvim/event/socket.c
Outdated
void socket_watcher_init(Loop *loop, SocketWatcher *watcher, | ||
const char *endpoint, void *data) | ||
void socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoint, | ||
void *data) | ||
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use FUNC_ATTR_NONNULL_ARG(1,2,3)
here.
src/nvim/event/socket.c
Outdated
void socket_watcher_init(Loop *loop, SocketWatcher *watcher, | ||
const char *endpoint, void *data) | ||
void socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoint, | ||
void *data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we missing a watcher->data = data
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just remove data
from the arguments? It's always called with NULL
and only in server_start()
. Maybe the original author planned ahead back then.
src/nvim/event/socket.c
Outdated
if (uv_ip4_addr(ip, port, &watcher->uv.tcp.addr)) { | ||
// Invalid address, treat as named pipe or unix socket | ||
tcp = false; | ||
char *addr = xstrdup(endpoint); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could copy endpoint directly into watcher->addr
and work with that string:
diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c
index 48d4c3716..c254b8077 100644
--- a/src/nvim/event/socket.c
+++ b/src/nvim/event/socket.c
@@ -25,12 +25,12 @@ void socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoin
void *data)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3)
{
- char *addr = xstrdup(endpoint);
+ xstrlcpy(watcher->addr, endpoint, sizeof(watcher->addr));
+ char *addr = watcher->addr;
char *host_end = strrchr(addr, ':');
if (host_end && addr != host_end) {
*host_end = '\0';
- xstrlcpy(watcher->addr, addr, sizeof(watcher->addr));
uv_getaddrinfo_t request;
int retval = uv_getaddrinfo(&loop->uv, &request, NULL, addr, host_end+1,
@@ -40,6 +40,7 @@ void socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoin
});
if (retval != 0) {
// Failed to look up address.
+ *host_end = ':';
goto do_pipe;
}
watcher->uv.tcp.addrinfo = request.addrinfo;
@@ -48,7 +49,6 @@ void socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoin
watcher->stream = (uv_stream_t *)&watcher->uv.tcp.handle;
} else {
do_pipe:
- xstrlcpy(watcher->addr, endpoint, sizeof(watcher->addr));
uv_pipe_init(&loop->uv, &watcher->uv.pipe.handle, 0);
watcher->stream = (uv_stream_t *)&watcher->uv.pipe.handle;
}
@@ -57,8 +57,6 @@ do_pipe:
watcher->cb = NULL;
watcher->close_cb = NULL;
watcher->events = NULL;
-
- xfree(addr);
}
int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
src/nvim/event/socket.c
Outdated
} | ||
result = uv_listen(watcher->stream, backlog, connection_cb); | ||
if (result == 0) { | ||
struct sockaddr_storage sas = *(struct sockaddr_storage *)ai->ai_addr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be struct sockaddr_storage sas;
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. Ooooh. Sorry, now I get what you meant earlier. I'll fix that.
With With |
|
@mhinz, it creates a tcp socket for me. |
@oni-link Hopefully it works as expected now.
I replaced |
3e93f3c
to
ca39453
Compare
@mhinz, port range check works.
|
You mean It would be weird, but at least on Unix it should be no problem. I'd blame the user in that case. :> And current master acts the same, so I'd say it's fine for now. |
The Linux QB failure is
which doesn't make sense because both The FreeBSD QB failure is
on the assert:
It'd be interesting to see what |
everything after it the port. If the port is empty or `0`, | ||
a random port will be assigned. | ||
|
||
If no address is given, it is equivalent to: > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this true also on windows (pipes have a disjoint namespace from regular files)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no idea. I trust the person who wrote it in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't be so trusting :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For Windows the special name format \\.\pipe\nvim-<PID>-<COUNTER>
is used.
src/nvim/event/socket.c
Outdated
uv_getaddrinfo_t request; | ||
|
||
int retval = uv_getaddrinfo(&loop->uv, &request, NULL, addr, port, | ||
&(struct addrinfo){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not declare hints as a separate var?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, there's no need to keep it, so I used a throwaway compound literal.
Hmm, if So |
Wouldn't it be useful if |
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 Server tests: use helpers.command() Server tests: endpoint parsing in serverstart() Server: don't fall back to Unix sockets Doc: explain the format for serverstart()
Why? You already calculate the correct value for serverlist, why not return it already? |
When using serverstart("ip.ad.d.r:") to listen on a random port, we need to abide by getaddrinfo()'s API and pass in a NULL service, rather than an empty string. When given an empty string, getaddrinfo() is free to search for a service by the given name (since the string isn't a number) which will fail. At least FreeBSD does perform this lookup.
Hmm, while testing the FreeBSD fix I ran into this:
by running |
Unfortunately, that would mean QB is red for everyone until we get off of that Ubuntu release. @jszakmeister is there any chance the QB Linux systems could be upgraded to at least Ubuntu 14.04 (Trusty)? In the meantime, I've added a cast which should silence this.
I've pushed a fix for that.
Agreed. I've also pushed a commit for this. |
src/nvim/event/socket.c
Outdated
@@ -45,6 +45,12 @@ int socket_watcher_init(Loop *loop, SocketWatcher *watcher, | |||
return UV_EINVAL; | |||
} | |||
|
|||
if (*port == NUL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this also be used if iport
is 0
or is "0"
always an acceptable port description?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A port that looks like a number is used directly, otherwise a lookup is performed. We should be fine here.
src/nvim/eval.c
Outdated
// "localhost:" will now have a port), return the final value to the user. | ||
size_t n; | ||
char **addrs = server_address_list(&n); | ||
address = addrs[n - 1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use address
instead of rettv->vval.v_string
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
@jamessan Yes I can. The real question, which has never been answered though, is what is our support policy? And I guess one of the additional headaches here is "what do we want to ensure we run against" vs. "what do we want to build against with no warnings"? Let me know what you guys want here (and it's generally best to do this via email directly to me--notifications from GitHub have a tendency to get lost in the noise). |
In the process of setting up the socket watcher, the address may be changed (e.g., adding the OS-selected port).
Although in_port_t is a typedef for uint16_t, GCC in Ubuntu 12.04 complains about potential loss of data due to converting int to uint16_t. Since we know this isn't possible, silence the warning to avoid breaking QB until it gets upgraded to a newer Ubuntu.
872fab2
to
62d020a
Compare
FEATURES: 0e873a3 Lua(Jit) built-in neovim#4411 5b32bce Windows: `:terminal` neovim#7007 7b0ceb3 UI/API: externalize cmdline neovim#7173 b67f58b UI/API: externalize wildmenu neovim#7454 b23aa1c UI: 'winhighlight' neovim#6597 17531ed UI: command-line coloring (`:help input()-highlight`) neovim#6364 244a1f9 API: execute lua directly from the remote api neovim#6704 45626de API: `get_keymap()` neovim#6236 db99982 API: `nvim_get_hl_by_name()`, `nvim_get_hl_by_id()` neovim#7082 dc68538 menu_get() function neovim#6322 9db42d4 :cquit : take an error code argument neovim#7336 9cc185d job-control: serverstart(): support ipv6 neovim#6680 1b7a9bf job-control: sockopen() neovim#6594 6efe84a clipboard: fallback to tmux clipboard neovim#6894 6016ac2 clipboard: customize clipboard with `g:clipboard` neovim#6030 3a86dd5 ruby: override ruby host via `g:ruby_host_prog` neovim#6841 16cce1a debug: $NVIM_LOG_FILE neovim#6827 0cba3da `:checkhealth` built-in, validates $VIMRUNTIME neovim#7399 FIXES: 105d680 TUI: more terminals, improve scroll/resize neovim#6816 cb912a3 :terminal : handle F1-F12, other keys neovim#7241 619838f inccommand: improve performance neovim#6949 04b3c32 inccommand: Fix matches for zero-width neovim#7487 60b1e8a inccommand: multiline, other fixes neovim#7315 f1f7f3b inccommand: Ignore leading modifiers in the command neovim#6967 1551f71 inccommand: fix 'gdefault' lockup neovim#7262 6338199 API: bufhl: support creating new groups neovim#7414 541dde3 API: allow K_EVENT during operator-pending 8c732f7 terminal: adjust for 'number' neovim#7440 5bec946 UI: preserve wildmenu during jobs/events neovim#7110 c349083 UI: disable 'lazyredraw' during ui_refresh. neovim#6259 51808a2 send FocusGained/FocusLost event instead of pseudokey neovim#7221 133f8bc shada: preserve unnamed register on restart neovim#4700 1b70a1d shada: avoid assertion on corrupt shada file neovim#6958 9f534f3 mksession: Restore tab-local working directory neovim#6859 de1084f fix buf_write() crash neovim#7140 7f76986 syntax: register 'Normal' highlight group neovim#6973 6e7a8c3 RPC: close channel if stream was closed neovim#7081 85f3084 clipboard: disallow recursion; show hint only once neovim#7203 8d1ccb6 clipboard: performance, avoid weird edge-cases neovim#7193 01487d4 'titleold' neovim#7358 01e53a5 Windows: better path-handling, separator (slash) hygiene neovim#7349 0f2873c Windows: multibyte startup arguments neovim#7060 CHANGES: 9ff0cc7 :terminal : start in normal-mode neovim#6808 032b088 lower priority of 'cursorcolumn', 'colorcolumn' neovim#7364 2a3bcd1 RPC: Don't delay notifications when request is pending neovim#6544 023f67c :terminal : Do not change 'number', 'relativenumber' neovim#6796 1ef2d76 socket.c: Disable Nagle's algorithm on TCP sockets neovim#6915 6720fe2 help: `K` tries Vim help instead of manpage neovim#3104 7068370 help, man.vim: change "outline" map to `gO` neovim#7405
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: