# HG changeset patch # User Bram Moolenaar # Date 1586707207 -7200 # Node ID e373843e29809b99915fa3fe522ed3cbae0051a6 # Parent 2bf41f30475cc0e13318c441daa9864c66b8082e patch 8.2.0557: no IPv6 support for channels Commit: https://github.com/vim/vim/commit/bfe13ccc58ccb96f243a58309800410db1ccb52c Author: Bram Moolenaar Date: Sun Apr 12 17:53:12 2020 +0200 patch 8.2.0557: no IPv6 support for channels Problem: No IPv6 support for channels. Solution: Add IPv6 support. (Ozaki Kiichi, closes https://github.com/vim/vim/issues/5893) diff --git a/.travis.yml b/.travis.yml --- a/.travis.yml +++ b/.travis.yml @@ -62,9 +62,7 @@ language: c sudo update-alternatives --install /usr/bin/lua lua /usr/bin/lua5.3 10 fi before_script: - # On travis bionic-amd64 gethostbyname() resolves "localhost" to 127.0.1.1 - # so that makes various channel tests fail. - - sudo sed -i '/^127\.0\.1\.1\s/s/\blocalhost\b//g' /etc/hosts + - sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0 - sudo bash ci/load-snd-dummy.sh || true - sudo usermod -a -G audio $USER - do_test() { sg audio "sg $(id -gn) '$*'"; } diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -120,6 +120,9 @@ Use |ch_status()| to see if the channel {address} has the form "hostname:port". E.g., "localhost:8765". +When using an IPv6 address, enclose it within square brackets. E.g., +"[2001:db8::1]:8765". + {options} is a dictionary with optional entries: *channel-open-options* "mode" can be: *channel-mode* @@ -621,6 +624,9 @@ ch_open({address} [, {options}]) *ch_ {address} has the form "hostname:port", e.g., "localhost:8765". + When using an IPv6 address, enclose it within square brackets. + E.g., "[2001:db8::1]:8765". + If {options} is given it must be a |Dictionary|. See |channel-open-options|. diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -375,6 +375,7 @@ m *+hangul_input* Hangul input support *+iconv* Compiled with the |iconv()| function *+iconv/dyn* Likewise |iconv-dynamic| |/dyn| T *+insert_expand* |insert_expand| Insert mode completion +m *+ipv6* Support for IPv6 networking |channel| m *+job* starting and stopping jobs |job| S *+jumplist* |jumplist| B *+keymap* |'keymap'| diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -850,7 +850,7 @@ endif ifeq ($(CHANNEL),yes) OBJ += $(OUTDIR)/channel.o -LIB += -lwsock32 +LIB += -lwsock32 -lws2_32 endif ifeq ($(DIRECTX),yes) diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -469,7 +469,7 @@ CHANNEL_PRO = proto/channel.pro CHANNEL_OBJ = $(OBJDIR)/channel.obj CHANNEL_DEFS = -DFEAT_JOB_CHANNEL -NETBEANS_LIB = WSock32.lib +NETBEANS_LIB = WSock32.lib Ws2_32.lib !endif # Set which version of the CRT to use diff --git a/src/auto/configure b/src/auto/configure --- a/src/auto/configure +++ b/src/auto/configure @@ -7723,8 +7723,7 @@ else fi if test "$enable_channel" = "yes"; then - - if test "x$HAIKU" = "xyes"; then + if test "x$HAIKU" = "xyes"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for socket in -lnetwork" >&5 $as_echo_n "checking for socket in -lnetwork... " >&6; } if ${ac_cv_lib_network_socket+:} false; then : @@ -7818,7 +7817,63 @@ fi fi - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for gethostbyname in -lnsl" >&5 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiling with IPv6 networking is possible" >&5 +$as_echo_n "checking whether compiling with IPv6 networking is possible... " >&6; } +if ${vim_cv_ipv6_networking+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + /* Check bitfields */ + struct nbbuf { + unsigned int initDone:1; + unsigned short signmaplen; + }; + +int +main () +{ + + /* Check creating a socket. */ + struct sockaddr_in server; + struct addrinfo *res; + (void)socket(AF_INET, SOCK_STREAM, 0); + (void)htons(100); + (void)getaddrinfo("microsoft.com", NULL, NULL, &res); + if (errno == ECONNREFUSED) + (void)connect(1, (struct sockaddr *)&server, sizeof(server)); + (void)freeaddrinfo(res); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + vim_cv_ipv6_networking="yes" +else + vim_cv_ipv6_networking="no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $vim_cv_ipv6_networking" >&5 +$as_echo "$vim_cv_ipv6_networking" >&6; } + + if test "x$vim_cv_ipv6_networking" = "xyes"; then + $as_echo "#define FEAT_IPV6 1" >>confdefs.h + + else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for gethostbyname in -lnsl" >&5 $as_echo_n "checking for gethostbyname in -lnsl... " >&6; } if ${ac_cv_lib_nsl_gethostbyname+:} false; then : $as_echo_n "(cached) " >&6 @@ -7863,8 +7918,11 @@ if test "x$ac_cv_lib_nsl_gethostbyname" fi - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiling with process communication is possible" >&5 -$as_echo_n "checking whether compiling with process communication is possible... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiling with IPv4 networking is possible" >&5 +$as_echo_n "checking whether compiling with IPv4 networking is possible... " >&6; } +if ${vim_cv_ipv4_networking+:} false; then : + $as_echo_n "(cached) " >&6 +else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -7900,14 +7958,16 @@ main () } _ACEOF if ac_fn_c_try_link "$LINENO"; then : - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; }; enable_netbeans="no"; enable_channel="no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext + vim_cv_ipv4_networking="yes" +else + vim_cv_ipv4_networking="no"; enable_netbeans="no"; enable_channel="no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $vim_cv_ipv4_networking" >&5 +$as_echo "$vim_cv_ipv4_networking" >&6; } + fi fi if test "$enable_netbeans" = "yes"; then $as_echo "#define FEAT_NETBEANS_INTG 1" >>confdefs.h diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -10,6 +10,13 @@ * Implements communication through a socket or any file handle. */ +#ifdef WIN32 +// Must include winsock2.h before windows.h since it conflicts with winsock.h +// (included in windows.h). +# include +# include +#endif + #include "vim.h" #if defined(FEAT_JOB_CHANNEL) || defined(PROTO) @@ -40,7 +47,7 @@ #else # include # include - +# include # include # ifdef HAVE_LIBGEN_H # include @@ -711,90 +718,38 @@ channel_gui_unregister(channel_T *channe static char *e_cannot_connect = N_("E902: Cannot connect to port"); /* - * Open a socket channel to "hostname":"port". - * "waittime" is the time in msec to wait for the connection. - * When negative wait forever. - * Returns the channel for success. - * Returns NULL for failure. + * For Unix we need to call connect() again after connect() failed. + * On Win32 one time is sufficient. */ - channel_T * -channel_open( - char *hostname, - int port_in, - int waittime, - void (*nb_close_cb)(void)) -{ - int sd = -1; - struct sockaddr_in server; - struct hostent *host; + static int +channel_connect( + channel_T *channel, + const struct sockaddr *server_addr, + int server_addrlen, + int *waittime) +{ + int sd = -1; #ifdef MSWIN - u_short port = port_in; - u_long val = 1; -#else - int port = port_in; -#endif - channel_T *channel; - int ret; - -#ifdef MSWIN - channel_init_winsock(); + u_long val = 1; #endif - channel = add_channel(); - if (channel == NULL) - { - ch_error(NULL, "Cannot allocate channel."); - return NULL; - } - - // Get the server internet address and put into addr structure - // fill in the socket address structure and connect to server - vim_memset((char *)&server, 0, sizeof(server)); - server.sin_family = AF_INET; - server.sin_port = htons(port); - if ((host = gethostbyname(hostname)) == NULL) - { - ch_error(channel, "in gethostbyname() in channel_open()"); - PERROR(_("E901: gethostbyname() in channel_open()")); - channel_free(channel); - return NULL; - } - { - char *p; - - // When using host->h_addr_list[0] directly ubsan warns for it to not - // be aligned. First copy the pointer to avoid that. - memcpy(&p, &host->h_addr_list[0], sizeof(p)); - memcpy((char *)&server.sin_addr, p, host->h_length); - } - - // On Mac and Solaris a zero timeout almost never works. At least wait - // one millisecond. Let's do it for all systems, because we don't know why - // this is needed. - if (waittime == 0) - waittime = 1; - - /* - * For Unix we need to call connect() again after connect() failed. - * On Win32 one time is sufficient. - */ while (TRUE) { long elapsed_msec = 0; int waitnow; + int ret; if (sd >= 0) sock_close(sd); - sd = socket(AF_INET, SOCK_STREAM, 0); + sd = socket(server_addr->sa_family, SOCK_STREAM, 0); if (sd == -1) { - ch_error(channel, "in socket() in channel_open()."); - PERROR(_("E898: socket() in channel_open()")); - channel_free(channel); - return NULL; + ch_error(channel, "in socket() in channel_connect()."); + PERROR(_("E898: socket() in channel_connect()")); + return -1; } - if (waittime >= 0) + if (*waittime >= 0) { // Make connect() non-blocking. if ( @@ -807,23 +762,22 @@ channel_open( { SOCK_ERRNO; ch_error(channel, - "channel_open: Connect failed with errno %d", errno); + "channel_connect: Connect failed with errno %d", errno); sock_close(sd); - channel_free(channel); - return NULL; + return -1; } } // Try connecting to the server. - ch_log(channel, "Connecting to %s port %d", hostname, port); - ret = connect(sd, (struct sockaddr *)&server, sizeof(server)); - + ch_log(channel, "Connecting..."); + + ret = connect(sd, server_addr, server_addrlen); if (ret == 0) // The connection could be established. break; SOCK_ERRNO; - if (waittime < 0 || (errno != EWOULDBLOCK + if (*waittime < 0 || (errno != EWOULDBLOCK && errno != ECONNREFUSED #ifdef EINPROGRESS && errno != EINPROGRESS @@ -831,22 +785,24 @@ channel_open( )) { ch_error(channel, - "channel_open: Connect failed with errno %d", errno); + "channel_connect: Connect failed with errno %d", errno); PERROR(_(e_cannot_connect)); sock_close(sd); - channel_free(channel); - return NULL; + return -1; + } + else if (errno == ECONNREFUSED) + { + ch_error(channel, "channel_connect: Connection refused"); + sock_close(sd); + return -1; } // Limit the waittime to 50 msec. If it doesn't work within this // time we close the socket and try creating it again. - waitnow = waittime > 50 ? 50 : waittime; + waitnow = *waittime > 50 ? 50 : *waittime; // If connect() didn't finish then try using select() to wait for the // connection to be made. For Win32 always use select() to wait. -#ifndef MSWIN - if (errno != ECONNREFUSED) -#endif { struct timeval tv; fd_set rfds; @@ -868,18 +824,17 @@ channel_open( gettimeofday(&start_tv, NULL); #endif ch_log(channel, - "Waiting for connection (waiting %d msec)...", waitnow); + "Waiting for connection (waiting %d msec)...", waitnow); + ret = select((int)sd + 1, &rfds, &wfds, NULL, &tv); - if (ret < 0) { SOCK_ERRNO; ch_error(channel, - "channel_open: Connect failed with errno %d", errno); + "channel_connect: Connect failed with errno %d", errno); PERROR(_(e_cannot_connect)); sock_close(sd); - channel_free(channel); - return NULL; + return -1; } #ifdef MSWIN @@ -888,9 +843,9 @@ channel_open( if (FD_ISSET(sd, &wfds)) break; elapsed_msec = waitnow; - if (waittime > 1 && elapsed_msec < waittime) + if (*waittime > 1 && elapsed_msec < *waittime) { - waittime -= elapsed_msec; + *waittime -= elapsed_msec; continue; } #else @@ -914,12 +869,17 @@ channel_open( )) { ch_error(channel, - "channel_open: Connect failed with errno %d", + "channel_connect: Connect failed with errno %d", so_error); PERROR(_(e_cannot_connect)); sock_close(sd); - channel_free(channel); - return NULL; + return -1; + } + else if (errno == ECONNREFUSED) + { + ch_error(channel, "channel_connect: Connection refused"); + sock_close(sd); + return -1; } } @@ -929,30 +889,30 @@ channel_open( gettimeofday(&end_tv, NULL); elapsed_msec = (end_tv.tv_sec - start_tv.tv_sec) * 1000 - + (end_tv.tv_usec - start_tv.tv_usec) / 1000; + + (end_tv.tv_usec - start_tv.tv_usec) / 1000; #endif } #ifndef MSWIN - if (waittime > 1 && elapsed_msec < waittime) + if (*waittime > 1 && elapsed_msec < *waittime) { // The port isn't ready but we also didn't get an error. // This happens when the server didn't open the socket // yet. Select() may return early, wait until the remaining // "waitnow" and try again. waitnow -= elapsed_msec; - waittime -= elapsed_msec; + *waittime -= elapsed_msec; if (waitnow > 0) { mch_delay((long)waitnow, TRUE); ui_breakcheck(); - waittime -= waitnow; + *waittime -= waitnow; } if (!got_int) { - if (waittime <= 0) + if (*waittime <= 0) // give it one more try - waittime = 1; + *waittime = 1; continue; } // we were interrupted, behave as if timed out @@ -962,13 +922,10 @@ channel_open( // We timed out. ch_error(channel, "Connection timed out"); sock_close(sd); - channel_free(channel); - return NULL; - } - - ch_log(channel, "Connection made"); - - if (waittime >= 0) + return -1; + } + + if (*waittime >= 0) { #ifdef MSWIN val = 0; @@ -978,10 +935,151 @@ channel_open( #endif } + return sd; +} + +/* + * Open a socket channel to "hostname":"port". + * "waittime" is the time in msec to wait for the connection. + * When negative wait forever. + * Returns the channel for success. + * Returns NULL for failure. + */ + channel_T * +channel_open( + const char *hostname, + int port, + int waittime, + void (*nb_close_cb)(void)) +{ + int sd = -1; + channel_T *channel = NULL; +#ifdef FEAT_IPV6 + struct addrinfo hints; + struct addrinfo *res = NULL; + struct addrinfo *addr = NULL; +#else + struct sockaddr_in server; + struct hostent *host = NULL; +#endif + +#ifdef MSWIN + channel_init_winsock(); +#endif + + channel = add_channel(); + if (channel == NULL) + { + ch_error(NULL, "Cannot allocate channel."); + return NULL; + } + + // Get the server internet address and put into addr structure fill in the + // socket address structure and connect to server. +#ifdef FEAT_IPV6 + vim_memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; +# ifdef AI_ADDRCONFIG + hints.ai_flags = AI_ADDRCONFIG; +# endif + // Set port number manually in order to prevent name resolution services + // from being invoked in the environment where AI_NUMERICSERV is not + // defined. + if (getaddrinfo(hostname, NULL, &hints, &res) != 0) + { + ch_error(channel, "in getaddrinfo() in channel_open()"); + PERROR(_("E901: getaddrinfo() in channel_open()")); + channel_free(channel); + return NULL; + } + + for (addr = res; addr != NULL; addr = addr->ai_next) + { + const char *dst = hostname; + const void *src = NULL; + char buf[NUMBUFLEN]; + + if (addr->ai_family == AF_INET6) + { + struct sockaddr_in6 *sai = (struct sockaddr_in6 *)addr->ai_addr; + + sai->sin6_port = htons(port); + src = &sai->sin6_addr; + } + else if (addr->ai_family == AF_INET) + { + struct sockaddr_in *sai = (struct sockaddr_in *)addr->ai_addr; + + sai->sin_port = htons(port); + src = &sai->sin_addr; + } + if (src != NULL) + { + dst = inet_ntop(addr->ai_family, src, buf, sizeof(buf)); + if (dst != NULL && STRCMP(hostname, dst) != 0) + ch_log(channel, "Resolved %s to %s", hostname, dst); + } + + ch_log(channel, "Trying to connect to %s port %d", dst, port); + + // On Mac and Solaris a zero timeout almost never works. At least wait + // one millisecond. Let's do it for all systems, because we don't know + // why this is needed. + if (waittime == 0) + waittime = 1; + + sd = channel_connect(channel, addr->ai_addr, addr->ai_addrlen, + &waittime); + if (sd >= 0) + break; + } + + freeaddrinfo(res); +#else + vim_memset((char *)&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_port = htons(port); + if ((host = gethostbyname(hostname)) == NULL) + { + ch_error(channel, "in gethostbyname() in channel_open()"); + PERROR(_("E901: gethostbyname() in channel_open()")); + channel_free(channel); + return NULL; + } + { + char *p; + + // When using host->h_addr_list[0] directly ubsan warns for it to not + // be aligned. First copy the pointer to avoid that. + memcpy(&p, &host->h_addr_list[0], sizeof(p)); + memcpy((char *)&server.sin_addr, p, host->h_length); + } + + ch_log(channel, "Trying to connect to %s port %d", hostname, port); + + // On Mac and Solaris a zero timeout almost never works. At least wait one + // millisecond. Let's do it for all systems, because we don't know why + // this is needed. + if (waittime == 0) + waittime = 1; + + sd = channel_connect(channel, (struct sockaddr *)&server, sizeof(server), + &waittime); +#endif + + if (sd < 0) + { + channel_free(channel); + return NULL; + } + + ch_log(channel, "Connection made"); + channel->CH_SOCK_FD = (sock_T)sd; channel->ch_nb_close_cb = nb_close_cb; channel->ch_hostname = (char *)vim_strsave((char_u *)hostname); - channel->ch_port = port_in; + channel->ch_port = port; channel->ch_to_be_closed |= (1U << PART_SOCK); #ifdef FEAT_GUI @@ -1222,6 +1320,7 @@ channel_open_func(typval_T *argvars) char_u *p; char *rest; int port; + int is_ipv6 = FALSE; jobopt_T opt; channel_T *channel = NULL; @@ -1234,20 +1333,40 @@ channel_open_func(typval_T *argvars) } // parse address - p = vim_strchr(address, ':'); - if (p == NULL) + if (*address == '[') + { + // ipv6 address + is_ipv6 = TRUE; + p = vim_strchr(address + 1, ']'); + if (p == NULL || *++p != ':') + { + semsg(_(e_invarg2), address); + return NULL; + } + } + else + { + p = vim_strchr(address, ':'); + if (p == NULL) + { + semsg(_(e_invarg2), address); + return NULL; + } + } + port = strtol((char *)(p + 1), &rest, 10); + if (*address == NUL || port <= 0 || port >= 65536 || *rest != NUL) { semsg(_(e_invarg2), address); return NULL; } - *p++ = NUL; - port = strtol((char *)p, &rest, 10); - if (*address == NUL || port <= 0 || *rest != NUL) - { - p[-1] = ':'; - semsg(_(e_invarg2), address); - return NULL; - } + if (is_ipv6) + { + // strip '[' and ']' + ++address; + *(p - 1) = NUL; + } + else + *p = NUL; // parse options clear_job_options(&opt); diff --git a/src/config.h.in b/src/config.h.in --- a/src/config.h.in +++ b/src/config.h.in @@ -438,6 +438,9 @@ /* Define if we have shl_load() */ #undef HAVE_SHL_LOAD +/* Define if we can use IPv6 networking. */ +#undef FEAT_IPV6 + /* Define if you want to include NetBeans integration. */ #undef FEAT_NETBEANS_INTG diff --git a/src/configure.ac b/src/configure.ac --- a/src/configure.ac +++ b/src/configure.ac @@ -2038,17 +2038,50 @@ else fi if test "$enable_channel" = "yes"; then - dnl On Solaris we need the socket and nsl library. - + dnl On Solaris we need the socket library, or on Haiku the network library. if test "x$HAIKU" = "xyes"; then AC_CHECK_LIB(network, socket) else AC_CHECK_LIB(socket, socket) fi - AC_CHECK_LIB(nsl, gethostbyname) - AC_MSG_CHECKING(whether compiling with process communication is possible) - AC_TRY_LINK([ + AC_CACHE_CHECK([whether compiling with IPv6 networking is possible], [vim_cv_ipv6_networking], + [AC_TRY_LINK([ +#include +#include +#include +#include +#include +#include +#include +#include +#include + /* Check bitfields */ + struct nbbuf { + unsigned int initDone:1; + unsigned short signmaplen; + }; + ], [ + /* Check creating a socket. */ + struct sockaddr_in server; + struct addrinfo *res; + (void)socket(AF_INET, SOCK_STREAM, 0); + (void)htons(100); + (void)getaddrinfo("microsoft.com", NULL, NULL, &res); + if (errno == ECONNREFUSED) + (void)connect(1, (struct sockaddr *)&server, sizeof(server)); + (void)freeaddrinfo(res); + ], + [vim_cv_ipv6_networking="yes"], + [vim_cv_ipv6_networking="no"])]) + + if test "x$vim_cv_ipv6_networking" = "xyes"; then + AC_DEFINE(FEAT_IPV6) + else + dnl On Solaris we need the nsl library. + AC_CHECK_LIB(nsl, gethostbyname) + AC_CACHE_CHECK([whether compiling with IPv4 networking is possible], [vim_cv_ipv4_networking], + [AC_TRY_LINK([ #include #include #include @@ -2072,8 +2105,9 @@ if test "$enable_channel" = "yes"; then if (errno == ECONNREFUSED) (void)connect(1, (struct sockaddr *)&server, sizeof(server)); ], - AC_MSG_RESULT(yes), - AC_MSG_RESULT(no); enable_netbeans="no"; enable_channel="no") + [vim_cv_ipv4_networking="yes"], + [vim_cv_ipv4_networking="no"; enable_netbeans="no"; enable_channel="no"])]) + fi fi if test "$enable_netbeans" = "yes"; then AC_DEFINE(FEAT_NETBEANS_INTG) diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -3943,6 +3943,13 @@ f_has(typval_T *argvars, typval_T *rettv #endif }, {"insert_expand", 1}, + {"ipv6", +#ifdef FEAT_IPV6 + 1 +#else + 0 +#endif + }, {"job", #ifdef FEAT_JOB_CHANNEL 1 diff --git a/src/proto/channel.pro b/src/proto/channel.pro --- a/src/proto/channel.pro +++ b/src/proto/channel.pro @@ -7,7 +7,7 @@ int channel_unref(channel_T *channel); int free_unused_channels_contents(int copyID, int mask); void free_unused_channels(int copyID, int mask); void channel_gui_register_all(void); -channel_T *channel_open(char *hostname, int port_in, int waittime, void (*nb_close_cb)(void)); +channel_T *channel_open(const char *hostname, int port, int waittime, void (*nb_close_cb)(void)); void channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err); void channel_set_job(channel_T *channel, job_T *job, jobopt_T *options); void channel_buffer_free(buf_T *buf); diff --git a/src/testdir/check.vim b/src/testdir/check.vim --- a/src/testdir/check.vim +++ b/src/testdir/check.vim @@ -142,4 +142,37 @@ func CheckEnglish() endif endfunc +" Command to check that loopback device has IPv6 address +command CheckIPv6 call CheckIPv6() +func CheckIPv6() + if !has('ipv6') + throw 'Skipped: cannot use IPv6 networking' + endif + if !exists('s:ipv6_loopback') + let s:ipv6_loopback = s:CheckIPv6Loopback() + endif + if !s:ipv6_loopback + throw 'Skipped: no IPv6 address for loopback device' + endif +endfunc + +func s:CheckIPv6Loopback() + if has('win32') + return system('netsh interface ipv6 show interface') =~? '\' + elseif filereadable('/proc/net/if_inet6') + return (match(readfile('/proc/net/if_inet6'), '\slo$') >= 0) + elseif executable('ifconfig') + for dev in ['lo0', 'lo', 'loop'] + " NOTE: On SunOS, need specify address family 'inet6' to get IPv6 info. + if system('ifconfig ' .. dev .. ' inet6 2>/dev/null') =~? '\' + \ || system('ifconfig ' .. dev .. ' 2>/dev/null') =~? '\' + return v:true + endif + endfor + else + " TODO: How to check it in other platforms? + endif + return v:false +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/runtest.vim b/src/testdir/runtest.vim --- a/src/testdir/runtest.vim +++ b/src/testdir/runtest.vim @@ -161,8 +161,7 @@ func RunTheTest(test) exe 'call ' . a:test else try - let s:test = a:test - au VimLeavePre * call EarlyExit(s:test) + au VimLeavePre * call EarlyExit(g:testfunc) exe 'call ' . a:test au! VimLeavePre catch /^\cskipped/ @@ -226,11 +225,11 @@ func AfterTheTest(func_name) if len(v:errors) > 0 if match(s:may_fail_list, '^' .. a:func_name) >= 0 let s:fail_expected += 1 - call add(s:errors_expected, 'Found errors in ' . s:test . ':') + call add(s:errors_expected, 'Found errors in ' . g:testfunc . ':') call extend(s:errors_expected, v:errors) else let s:fail += 1 - call add(s:errors, 'Found errors in ' . s:test . ':') + call add(s:errors, 'Found errors in ' . g:testfunc . ':') call extend(s:errors, v:errors) endif let v:errors = [] @@ -396,31 +395,31 @@ endif let s:may_fail_list = [] if $TEST_MAY_FAIL != '' - " Split the list at commas and add () to make it match s:test. + " Split the list at commas and add () to make it match g:testfunc. let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'}) endif " Execute the tests in alphabetical order. -for s:test in sort(s:tests) +for g:testfunc in sort(s:tests) " Silence, please! set belloff=all let prev_error = '' let total_errors = [] let g:run_nr = 1 - " A test can set test_is_flaky to retry running the test. - let test_is_flaky = 0 + " A test can set g:test_is_flaky to retry running the test. + let g:test_is_flaky = 0 - call RunTheTest(s:test) + call RunTheTest(g:testfunc) " Repeat a flaky test. Give up when: " - it fails again with the same message " - it fails five times (with a different message) if len(v:errors) > 0 - \ && (index(s:flaky_tests, s:test) >= 0 - \ || test_is_flaky) + \ && (index(s:flaky_tests, g:testfunc) >= 0 + \ || g:test_is_flaky) while 1 - call add(s:messages, 'Found errors in ' . s:test . ':') + call add(s:messages, 'Found errors in ' . g:testfunc . ':') call extend(s:messages, v:errors) call add(total_errors, 'Run ' . g:run_nr . ':') @@ -443,7 +442,7 @@ for s:test in sort(s:tests) let v:errors = [] let g:run_nr += 1 - call RunTheTest(s:test) + call RunTheTest(g:testfunc) if len(v:errors) == 0 " Test passed on rerun. @@ -452,7 +451,7 @@ for s:test in sort(s:tests) endwhile endif - call AfterTheTest(s:test) + call AfterTheTest(g:testfunc) endfor call FinishTesting() diff --git a/src/testdir/test_cdo.vim b/src/testdir/test_cdo.vim --- a/src/testdir/test_cdo.vim +++ b/src/testdir/test_cdo.vim @@ -4,7 +4,7 @@ source check.vim CheckFeature quickfix " Create the files used by the tests -function SetUp() +func SetUp() call writefile(["Line1", "Line2", "Line3"], 'Xtestfile1') call writefile(["Line1", "Line2", "Line3"], 'Xtestfile2') call writefile(["Line1", "Line2", "Line3"], 'Xtestfile3') diff --git a/src/testdir/test_channel.py b/src/testdir/test_channel.py --- a/src/testdir/test_channel.py +++ b/src/testdir/test_channel.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # # Server that will accept connections from a Vim channel. # Used by test_channel.vim. @@ -235,21 +235,19 @@ def writePortInFile(port): f.write("{0}".format(port)) f.close() -if __name__ == "__main__": - HOST, PORT = "localhost", 0 - +def main(host, port, server_class=ThreadedTCPServer): # Wait half a second before opening the port to test waittime in ch_open(). # We do want to get the port number, get that first. We cannot open the # socket, guess a port is free. if len(sys.argv) >= 2 and sys.argv[1] == 'delay': - PORT = 13684 - writePortInFile(PORT) + port = 13684 + writePortInFile(port) print("Wait for it...") time.sleep(0.5) - server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler) - ip, port = server.server_address + server = server_class((host, port), ThreadedTCPRequestHandler) + ip, port = server.server_address[0:2] # Start a thread with the server. That thread will then start a new thread # for each connection. @@ -263,7 +261,10 @@ if __name__ == "__main__": # Main thread terminates, but the server continues running # until server.shutdown() is called. try: - while server_thread.isAlive(): + while server_thread.is_alive(): server_thread.join(1) except (KeyboardInterrupt, SystemExit): server.shutdown() + +if __name__ == "__main__": + main("localhost", 0) diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -18,11 +18,21 @@ endif " Add ch_log() calls where you want to see what happens. " call ch_logfile('channellog', 'w') -let s:chopt = {} +func SetUp() + if g:testfunc =~ '_ipv6()$' + let s:localhost = '[::1]:' + let s:testscript = 'test_channel_6.py' + else + let s:localhost = 'localhost:' + let s:testscript = 'test_channel.py' + endif + let s:chopt = {} + call ch_log(g:testfunc) +endfunc " Run "testfunc" after starting the server and stop the server afterwards. func s:run_server(testfunc, ...) - call RunServer('test_channel.py', a:testfunc, a:000) + call RunServer(s:testscript, a:testfunc, a:000) " communicating with a server can be flaky let g:test_is_flaky = 1 @@ -54,9 +64,7 @@ func Ch_communicate(port) let s:chopt.drop = 'never' " Also add the noblock flag to try it out. let s:chopt.noblock = 1 - let handle = ch_open('localhost:' . a:port, s:chopt) - unlet s:chopt.drop - unlet s:chopt.noblock + let handle = ch_open(s:localhost . a:port, s:chopt) if ch_status(handle) == "fail" call assert_report("Can't open channel") return @@ -224,13 +232,17 @@ func Ch_communicate(port) endfunc func Test_communicate() - call ch_log('Test_communicate()') call s:run_server('Ch_communicate') endfunc +func Test_communicate_ipv6() + CheckIPv6 + call Test_communicate() +endfunc + " Test that we can open two channels. func Ch_two_channels(port) - let handle = ch_open('localhost:' . a:port, s:chopt) + let handle = ch_open(s:localhost . a:port, s:chopt) call assert_equal(v:t_channel, type(handle)) if handle->ch_status() == "fail" call assert_report("Can't open channel") @@ -239,7 +251,7 @@ func Ch_two_channels(port) call assert_equal('got it', ch_evalexpr(handle, 'hello!')) - let newhandle = ch_open('localhost:' . a:port, s:chopt) + let newhandle = ch_open(s:localhost . a:port, s:chopt) if ch_status(newhandle) == "fail" call assert_report("Can't open second channel") return @@ -259,9 +271,14 @@ func Test_two_channels() call s:run_server('Ch_two_channels') endfunc +func Test_two_channels_ipv6() + CheckIPv6 + call Test_two_channels() +endfunc + " Test that a server crash is handled gracefully. func Ch_server_crash(port) - let handle = ch_open('localhost:' . a:port, s:chopt) + let handle = ch_open(s:localhost . a:port, s:chopt) if ch_status(handle) == "fail" call assert_report("Can't open channel") return @@ -273,10 +290,14 @@ func Ch_server_crash(port) endfunc func Test_server_crash() - call ch_log('Test_server_crash()') call s:run_server('Ch_server_crash') endfunc +func Test_server_crash_ipv6() + CheckIPv6 + call Test_server_crash() +endfunc + """"""""" func Ch_handler(chan, msg) @@ -286,7 +307,7 @@ func Ch_handler(chan, msg) endfunc func Ch_channel_handler(port) - let handle = ch_open('localhost:' . a:port, s:chopt) + let handle = ch_open(s:localhost . a:port, s:chopt) if ch_status(handle) == "fail" call assert_report("Can't open channel") return @@ -302,14 +323,17 @@ func Ch_channel_handler(port) endfunc func Test_channel_handler() - call ch_log('Test_channel_handler()') let g:Ch_reply = "" let s:chopt.callback = 'Ch_handler' call s:run_server('Ch_channel_handler') let g:Ch_reply = "" let s:chopt.callback = function('Ch_handler') call s:run_server('Ch_channel_handler') - unlet s:chopt.callback +endfunc + +func Test_channel_handler_ipv6() + CheckIPv6 + call Test_channel_handler() endfunc """"""""" @@ -327,7 +351,7 @@ func Ch_oneHandler(chan, msg) endfunc func Ch_channel_zero(port) - let handle = ('localhost:' .. a:port)->ch_open(s:chopt) + let handle = (s:localhost .. a:port)->ch_open(s:chopt) if ch_status(handle) == "fail" call assert_report("Can't open channel") return @@ -359,7 +383,6 @@ func Ch_channel_zero(port) endfunc func Test_zero_reply() - call ch_log('Test_zero_reply()') " Run with channel handler let s:has_handler = 1 let s:chopt.callback = 'Ch_zeroHandler' @@ -371,6 +394,11 @@ func Test_zero_reply() call s:run_server('Ch_channel_zero') endfunc +func Test_zero_reply_ipv6() + CheckIPv6 + call Test_zero_reply() +endfunc + """"""""" let g:Ch_reply1 = "" @@ -392,7 +420,7 @@ func Ch_handleRaw3(chan, msg) endfunc func Ch_raw_one_time_callback(port) - let handle = ch_open('localhost:' . a:port, s:chopt) + let handle = ch_open(s:localhost . a:port, s:chopt) if ch_status(handle) == "fail" call assert_report("Can't open channel") return @@ -410,10 +438,14 @@ func Ch_raw_one_time_callback(port) endfunc func Test_raw_one_time_callback() - call ch_log('Test_raw_one_time_callback()') call s:run_server('Ch_raw_one_time_callback') endfunc +func Test_raw_one_time_callback_ipv6() + CheckIPv6 + call Test_raw_one_time_callback() +endfunc + """"""""" " Test that trying to connect to a non-existing port fails quickly. @@ -422,7 +454,6 @@ func Test_connect_waittime() " this is timing sensitive let g:test_is_flaky = 1 - call ch_log('Test_connect_waittime()') let start = reltime() let handle = ch_open('localhost:9876', s:chopt) if ch_status(handle) != "fail" @@ -752,7 +783,6 @@ func Test_close_output_buffer() enew! let test_lines = ['one', 'two'] call setline(1, test_lines) - call ch_log('Test_close_output_buffer()') let options = {'out_io': 'buffer'} let options['out_name'] = 'buffer-output' let options['out_msg'] = 0 @@ -924,17 +954,14 @@ func Run_pipe_through_sort(all, use_buff endfunc func Test_pipe_through_sort_all() - call ch_log('Test_pipe_through_sort_all()') call Run_pipe_through_sort(1, 1) endfunc func Test_pipe_through_sort_some() - call ch_log('Test_pipe_through_sort_some()') call Run_pipe_through_sort(0, 1) endfunc func Test_pipe_through_sort_feed() - call ch_log('Test_pipe_through_sort_feed()') call Run_pipe_through_sort(1, 0) endfunc @@ -1341,16 +1368,20 @@ endfunc " Test that "unlet handle" in a handler doesn't crash Vim. func Ch_unlet_handle(port) - let s:channelfd = ch_open('localhost:' . a:port, s:chopt) + let s:channelfd = ch_open(s:localhost . a:port, s:chopt) eval s:channelfd->ch_sendexpr("test", {'callback': function('s:UnletHandler')}) call WaitForAssert({-> assert_equal('what?', g:Ch_unletResponse)}) endfunc func Test_unlet_handle() - call ch_log('Test_unlet_handle()') call s:run_server('Ch_unlet_handle') endfunc +func Test_unlet_handle_ipv6() + CheckIPv6 + call Test_unlet_handle() +endfunc + """""""""" let g:Ch_unletResponse = '' @@ -1361,7 +1392,7 @@ endfunc " Test that "unlet handle" in a handler doesn't crash Vim. func Ch_close_handle(port) - let s:channelfd = ch_open('localhost:' . a:port, s:chopt) + let s:channelfd = ch_open(s:localhost . a:port, s:chopt) call ch_sendexpr(s:channelfd, "test", {'callback': function('Ch_CloseHandler')}) call WaitForAssert({-> assert_equal('what?', g:Ch_unletResponse)}) endfunc @@ -1370,6 +1401,23 @@ func Test_close_handle() call s:run_server('Ch_close_handle') endfunc +func Test_close_handle_ipv6() + CheckIPv6 + call Test_close_handle() +endfunc + +"""""""""" + +func Ch_open_ipv6(port) + let handle = ch_open('[::1]:' .. a:port, s:chopt) + call assert_notequal('fail', ch_status(handle)) +endfunc + +func Test_open_ipv6() + CheckIPv6 + call s:run_server('Ch_open_ipv6') +endfunc + """""""""" func Test_open_fail() @@ -1378,6 +1426,7 @@ func Test_open_fail() let d = ch call assert_fails("let ch = ch_open('noserver', 10)", 'E474:') call assert_fails("let ch = ch_open('localhost:-1')", 'E475:') + call assert_fails("let ch = ch_open('localhost:65537')", 'E475:') call assert_fails("let ch = ch_open('localhost:8765', {'timeout' : -1})", \ 'E474:') call assert_fails("let ch = ch_open('localhost:8765', {'axby' : 1})", @@ -1386,6 +1435,9 @@ func Test_open_fail() \ 'E475:') call assert_fails("let ch = ch_open('localhost:8765', {'part' : 'out'})", \ 'E475:') + call assert_fails("let ch = ch_open('[::]')", 'E475:') + call assert_fails("let ch = ch_open('[::.80')", 'E475:') + call assert_fails("let ch = ch_open('[::]8080')", 'E475:') endfunc func Test_ch_info_fail() @@ -1397,8 +1449,7 @@ endfunc func Ch_open_delay(port) " Wait up to a second for the port to open. let s:chopt.waittime = 1000 - let channel = ch_open('localhost:' . a:port, s:chopt) - unlet s:chopt.waittime + let channel = ch_open(s:localhost . a:port, s:chopt) if ch_status(channel) == "fail" call assert_report("Can't open channel") return @@ -1412,6 +1463,11 @@ func Test_open_delay() call s:run_server('Ch_open_delay', 'delay') endfunc +func Test_open_delay_ipv6() + CheckIPv6 + call Test_open_delay() +endfunc + """"""""" function MyFunction(a,b,c) @@ -1419,7 +1475,7 @@ function MyFunction(a,b,c) endfunc function Ch_test_call(port) - let handle = ch_open('localhost:' . a:port, s:chopt) + let handle = ch_open(s:localhost . a:port, s:chopt) if ch_status(handle) == "fail" call assert_report("Can't open channel") return @@ -1438,6 +1494,11 @@ func Test_call() call s:run_server('Ch_test_call') endfunc +func Test_call_ipv6() + CheckIPv6 + call Test_call() +endfunc + """"""""" let g:Ch_job_exit_ret = 'not yet' @@ -1513,7 +1574,7 @@ function MyCloseCb(ch) endfunc function Ch_test_close_callback(port) - let handle = ch_open('localhost:' . a:port, s:chopt) + let handle = ch_open(s:localhost . a:port, s:chopt) if ch_status(handle) == "fail" call assert_report("Can't open channel") return @@ -1528,8 +1589,13 @@ func Test_close_callback() call s:run_server('Ch_test_close_callback') endfunc +func Test_close_callback_ipv6() + CheckIPv6 + call Test_close_callback() +endfunc + function Ch_test_close_partial(port) - let handle = ch_open('localhost:' . a:port, s:chopt) + let handle = ch_open(s:localhost . a:port, s:chopt) if ch_status(handle) == "fail" call assert_report("Can't open channel") return @@ -1549,6 +1615,11 @@ func Test_close_partial() call s:run_server('Ch_test_close_partial') endfunc +func Test_close_partial_ipv6() + CheckIPv6 + call Test_close_partial() +endfunc + func Test_job_start_fails() " this was leaking memory call assert_fails("call job_start([''])", "E474:") @@ -1808,7 +1879,7 @@ func Test_cwd() endfunc function Ch_test_close_lambda(port) - let handle = ch_open('localhost:' . a:port, s:chopt) + let handle = ch_open(s:localhost . a:port, s:chopt) if ch_status(handle) == "fail" call assert_report("Can't open channel") return @@ -1824,6 +1895,11 @@ func Test_close_lambda() call s:run_server('Ch_test_close_lambda') endfunc +func Test_close_lambda_ipv6() + CheckIPv6 + call Test_close_lambda() +endfunc + func s:test_list_args(cmd, out, remove_lf) try let g:out = '' diff --git a/src/testdir/test_channel_6.py b/src/testdir/test_channel_6.py new file mode 100644 --- /dev/null +++ b/src/testdir/test_channel_6.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# +# Server that will accept connections from a Vim channel. +# Used by test_channel.vim. +# +# This requires Python 2.6 or later. + +from test_channel import main, ThreadedTCPServer +import socket + +class ThreadedTCP6Server(ThreadedTCPServer): + address_family = socket.AF_INET6 + +if __name__ == "__main__": + main("::", 0, ThreadedTCP6Server) diff --git a/src/testdir/test_escaped_glob.vim b/src/testdir/test_escaped_glob.vim --- a/src/testdir/test_escaped_glob.vim +++ b/src/testdir/test_escaped_glob.vim @@ -1,7 +1,7 @@ " Test whether glob()/globpath() return correct results with certain escaped " characters. -function SetUp() +func SetUp() " consistent sorting of file names set nofileignorecase endfunction diff --git a/src/testdir/test_getcwd.vim b/src/testdir/test_getcwd.vim --- a/src/testdir/test_getcwd.vim +++ b/src/testdir/test_getcwd.vim @@ -24,7 +24,7 @@ endfunc " Do all test in a separate window to avoid E211 when we recursively " delete the Xtopdir directory during cleanup -function SetUp() +func SetUp() set visualbell set nocp viminfo+=nviminfo diff --git a/src/testdir/test_hide.vim b/src/testdir/test_hide.vim --- a/src/testdir/test_hide.vim +++ b/src/testdir/test_hide.vim @@ -1,6 +1,6 @@ " Tests for :hide command/modifier and 'hidden' option -function SetUp() +func SetUp() let s:save_hidden = &hidden let s:save_bufhidden = &bufhidden let s:save_autowrite = &autowrite diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -739,6 +739,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 557, +/**/ 556, /**/ 555,