diff options
-rw-r--r-- | doc/mscp.1.in | 10 | ||||
-rw-r--r-- | doc/mscp.rst | 10 | ||||
-rw-r--r-- | include/mscp.h | 1 | ||||
-rw-r--r-- | patch/libssh-0.10.6-2-g6f1b1e76.patch | 112 | ||||
-rwxr-xr-x | scripts/test-in-container.sh | 2 | ||||
-rw-r--r-- | src/main.c | 20 | ||||
-rw-r--r-- | src/ssh.c | 6 | ||||
-rw-r--r-- | test/test_e2e.py | 33 |
8 files changed, 174 insertions, 20 deletions
diff --git a/doc/mscp.1.in b/doc/mscp.1.in index affaf5f..e3147d6 100644 --- a/doc/mscp.1.in +++ b/doc/mscp.1.in @@ -6,7 +6,7 @@ mscp \- copy files over multiple SSH connections .SH SYNOPSIS .B mscp -.RB [ \-vqDpHdNh ] +.RB [ \-46vqDpHdNh ] [\c .BI \-n \ NR_CONNECTIONS\c ] @@ -151,6 +151,14 @@ value is 16384. Note that the SSH specification restricts buffer size delivered over SSH. Changing this value is not recommended at present. .TP +.B \-4 +Uses IPv4 addresses only. + +.TP +.B \-6 +Uses IPv6 addresses only. + +.TP .B \-v Increments the verbose output level. diff --git a/doc/mscp.rst b/doc/mscp.rst index 1c76a4c..a835fff 100644 --- a/doc/mscp.rst +++ b/doc/mscp.rst @@ -2,7 +2,7 @@ MSCP ==== -:Date: v0.1.3-22-g9608400 +:Date: v0.1.3-23-ga9c59f7 NAME ==== @@ -12,7 +12,7 @@ mscp - copy files over multiple SSH connections SYNOPSIS ======== -**mscp** [**-vqDpHdNh**] [ **-n**\ *NR_CONNECTIONS* ] [ +**mscp** [**-46vqDpHdNh**] [ **-n**\ *NR_CONNECTIONS* ] [ **-m**\ *COREMASK* ] [ **-u**\ *MAX_STARTUPS* ] [ **-I**\ *INTERVAL* ] [ **-s**\ *MIN_CHUNK_SIZE* ] [ **-S**\ *MAX_CHUNK_SIZE* ] [ **-a**\ *NR_AHEAD* ] [ **-b**\ *BUF_SIZE* ] [ **-l**\ *LOGIN_NAME* ] [ @@ -87,6 +87,12 @@ OPTIONS delivered over SSH. Changing this value is not recommended at present. +**-4** + Uses IPv4 addresses only. + +**-6** + Uses IPv6 addresses only. + **-v** Increments the verbose output level. diff --git a/include/mscp.h b/include/mscp.h index 3fafd7b..83d4920 100644 --- a/include/mscp.h +++ b/include/mscp.h @@ -58,6 +58,7 @@ struct mscp_ssh_opts { /* ssh options */ char *login_name; /** ssh username */ char *port; /** ssh port */ + int ai_family; /** address family */ char *config; /** path to ssh_config, default ~/.ssh/config*/ char *identity; /** path to private key */ char *cipher; /** cipher spec */ diff --git a/patch/libssh-0.10.6-2-g6f1b1e76.patch b/patch/libssh-0.10.6-2-g6f1b1e76.patch index 2786da8..b5c88bc 100644 --- a/patch/libssh-0.10.6-2-g6f1b1e76.patch +++ b/patch/libssh-0.10.6-2-g6f1b1e76.patch @@ -37,10 +37,18 @@ index 1fce7b76..b64d1455 100644 int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len); diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h -index 669a0a96..b6a93ac7 100644 +index 669a0a96..da5b4099 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h -@@ -402,6 +402,7 @@ enum ssh_options_e { +@@ -368,6 +368,7 @@ enum ssh_options_e { + SSH_OPTIONS_HOST, + SSH_OPTIONS_PORT, + SSH_OPTIONS_PORT_STR, ++ SSH_OPTIONS_AI_FAMILY, + SSH_OPTIONS_FD, + SSH_OPTIONS_USER, + SSH_OPTIONS_SSH_DIR, +@@ -402,6 +403,7 @@ enum ssh_options_e { SSH_OPTIONS_GSSAPI_AUTH, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, SSH_OPTIONS_NODELAY, @@ -48,7 +56,7 @@ index 669a0a96..b6a93ac7 100644 SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, SSH_OPTIONS_PROCESS_CONFIG, SSH_OPTIONS_REKEY_DATA, -@@ -833,6 +834,7 @@ LIBSSH_API const char* ssh_get_hmac_in(ssh_session session); +@@ -833,6 +835,7 @@ LIBSSH_API const char* ssh_get_hmac_in(ssh_session session); LIBSSH_API const char* ssh_get_hmac_out(ssh_session session); LIBSSH_API ssh_buffer ssh_buffer_new(void); @@ -56,7 +64,7 @@ index 669a0a96..b6a93ac7 100644 LIBSSH_API void ssh_buffer_free(ssh_buffer buffer); #define SSH_BUFFER_FREE(x) \ do { if ((x) != NULL) { ssh_buffer_free(x); x = NULL; } } while(0) -@@ -843,6 +845,8 @@ LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer); +@@ -843,6 +846,8 @@ LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer); LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer); LIBSSH_API int ssh_session_set_disconnect_message(ssh_session session, const char *message); @@ -66,10 +74,18 @@ index 669a0a96..b6a93ac7 100644 #include "libssh/legacy.h" #endif diff --git a/include/libssh/session.h b/include/libssh/session.h -index 97936195..e4a7f80c 100644 +index 97936195..e4fc4fce 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h -@@ -258,6 +258,7 @@ struct ssh_session_struct { +@@ -249,6 +249,7 @@ struct ssh_session_struct { + unsigned long timeout; /* seconds */ + unsigned long timeout_usec; + uint16_t port; ++ int ai_family; + socket_t fd; + int StrictHostKeyChecking; + char compressionlevel; +@@ -258,6 +259,7 @@ struct ssh_session_struct { int flags; int exp_flags; int nodelay; @@ -204,9 +220,27 @@ index 8991e006..e0414801 100644 * @brief Ensure the buffer has at least a certain preallocated size. * diff --git a/src/connect.c b/src/connect.c -index 15cae644..e7520f40 100644 +index 15cae644..02ef43b4 100644 --- a/src/connect.c +++ b/src/connect.c +@@ -114,7 +114,7 @@ static int ssh_connect_socket_close(socket_t s) + #endif + } + +-static int getai(const char *host, int port, struct addrinfo **ai) ++static int getai(const char *host, int port, int ai_family, struct addrinfo **ai) + { + const char *service = NULL; + struct addrinfo hints; +@@ -123,7 +123,7 @@ static int getai(const char *host, int port, struct addrinfo **ai) + ZERO_STRUCT(hints); + + hints.ai_protocol = IPPROTO_TCP; +- hints.ai_family = PF_UNSPEC; ++ hints.ai_family = ai_family > 0 ? ai_family : PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if (port == 0) { @@ -156,6 +156,20 @@ static int set_tcp_nodelay(socket_t socket) sizeof(opt)); } @@ -228,6 +262,24 @@ index 15cae644..e7520f40 100644 /** * @internal * +@@ -173,7 +187,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + struct addrinfo *ai = NULL; + struct addrinfo *itr = NULL; + +- rc = getai(host, port, &ai); ++ rc = getai(host, port, session->opts.ai_family, &ai); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to resolve hostname %s (%s)", +@@ -199,7 +213,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + + SSH_LOG(SSH_LOG_PACKET, "Resolving %s", bind_addr); + +- rc = getai(bind_addr, 0, &bind_ai); ++ rc = getai(bind_addr, 0, session->opts.ai_family, &bind_ai); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to resolve bind address %s (%s)", @@ -256,6 +270,18 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, } } @@ -248,7 +300,7 @@ index 15cae644..e7520f40 100644 rc = connect(s, itr->ai_addr, itr->ai_addrlen); if (rc == -1 && (errno != 0) && (errno != EINPROGRESS)) { diff --git a/src/options.c b/src/options.c -index b3ecffe1..fb966fa1 100644 +index b3ecffe1..8de24ed6 100644 --- a/src/options.c +++ b/src/options.c @@ -217,6 +217,7 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) @@ -259,7 +311,17 @@ index b3ecffe1..fb966fa1 100644 new->opts.config_processed = src->opts.config_processed; new->common.log_verbosity = src->common.log_verbosity; new->common.callbacks = src->common.callbacks; -@@ -458,6 +459,10 @@ int ssh_options_set_algo(ssh_session session, +@@ -268,6 +269,9 @@ int ssh_options_set_algo(ssh_session session, + * - SSH_OPTIONS_PORT_STR: + * The port to connect to (const char *). + * ++ * - SSH_OPTIONS_AI_FAMILY: ++ * The address family for connecting (int *). ++ * + * - SSH_OPTIONS_FD: + * The file descriptor to use (socket_t).\n + * \n +@@ -458,6 +462,10 @@ int ssh_options_set_algo(ssh_session session, * Set it to disable Nagle's Algorithm (TCP_NODELAY) on the * session socket. (int, 0=false) * @@ -270,7 +332,29 @@ index b3ecffe1..fb966fa1 100644 * - SSH_OPTIONS_PROCESS_CONFIG * Set it to false to disable automatic processing of per-user * and system-wide OpenSSH configuration files. LibSSH -@@ -1017,6 +1022,20 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, +@@ -571,6 +579,21 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, + session->opts.port = i & 0xffffU; + } + break; ++ case SSH_OPTIONS_AI_FAMILY: ++ if (value == NULL) { ++ session->opts.ai_family = 0; ++ ssh_set_error_invalid(session); ++ return -1; ++ } else { ++ int *x = (int *) value; ++ if (*x < 0) { ++ session->opts.ai_family = 0; ++ ssh_set_error_invalid(session); ++ return -1; ++ } ++ session->opts.ai_family = *x; ++ } ++ break; + case SSH_OPTIONS_FD: + if (value == NULL) { + session->opts.fd = SSH_INVALID_SOCKET; +@@ -1017,6 +1040,20 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.nodelay = (*x & 0xff) > 0 ? 1 : 0; } break; @@ -292,10 +376,14 @@ index b3ecffe1..fb966fa1 100644 if (value == NULL) { ssh_set_error_invalid(session); diff --git a/src/session.c b/src/session.c -index 8c509699..88602b6a 100644 +index 8c509699..307388e5 100644 --- a/src/session.c +++ b/src/session.c -@@ -108,6 +108,7 @@ ssh_session ssh_new(void) +@@ -105,9 +105,11 @@ ssh_session ssh_new(void) + /* OPTIONS */ + session->opts.StrictHostKeyChecking = 1; + session->opts.port = 22; ++ session->opts.ai_family = 0; session->opts.fd = -1; session->opts.compressionlevel = 7; session->opts.nodelay = 0; diff --git a/scripts/test-in-container.sh b/scripts/test-in-container.sh index cdaef47..c71d278 100755 --- a/scripts/test-in-container.sh +++ b/scripts/test-in-container.sh @@ -15,6 +15,8 @@ if [ ! -e /var/run/sshd.pid ]; then fi ssh-keyscan localhost >> ${HOME}/.ssh/known_hosts +ssh-keyscan 127.0.0.1 >> ${HOME}/.ssh/known_hosts +ssh-keyscan ::1 >> ${HOME}/.ssh/known_hosts # Run test python3 -m pytest ../test -v @@ -9,6 +9,8 @@ #include <sys/time.h> #include <sys/ioctl.h> #include <poll.h> +#include <netinet/in.h> +#include <sys/socket.h> #include <pthread.h> #include <mscp.h> @@ -20,12 +22,12 @@ void usage(bool print_help) { - printf("mscp " MSCP_BUILD_VERSION ": copy files over multiple ssh connections\n" + printf("mscp " MSCP_BUILD_VERSION ": copy files over multiple SSH connections\n" "\n" - "Usage: mscp [vqDpHdNh] [-n nr_conns] [-m coremask]\n" + "Usage: mscp [-46vqDpHdNh] [-n nr_conns] [-m coremask]\n" " [-u max_startups] [-I interval]\n" " [-s min_chunk_sz] [-S max_chunk_sz] [-a nr_ahead] [-b buf_sz]\n" - " [-l login_name] [-p port] [-F ssh_config] [-i identity_file]\n" + " [-l login_name] [-P port] [-F ssh_config] [-i identity_file]\n" " [-c cipher_spec] [-M hmac_spec] [-C compress] [-g congestion]\n" " source ... target\n" "\n"); @@ -45,6 +47,8 @@ void usage(bool print_help) " -a NR_AHEAD number of inflight SFTP commands (default: 32)\n" " -b BUF_SZ buffer size for i/o and transfer\n" "\n" + " -4 use IPv4\n" + " -6 use IPv6\n" " -v increment verbose output level\n" " -q disable output\n" " -D dry run. check copy destinations with -vvv\n" @@ -263,8 +267,8 @@ int main(int argc, char **argv) memset(&o, 0, sizeof(o)); o.severity = MSCP_SEVERITY_WARN; - while ((ch = getopt(argc, argv, "n:m:u:I:s:S:a:b:vqDrl:P:i:F:c:M:C:g:pHdNh")) != - -1) { +#define mscpopts "n:m:u:I:s:S:a:b:46vqDrl:P:i:F:c:M:C:g:pHdNh" + while ((ch = getopt(argc, argv, mscpopts)) != -1) { switch (ch) { case 'n': o.nr_threads = atoi(optarg); @@ -294,6 +298,12 @@ int main(int argc, char **argv) case 'b': o.buf_sz = atoi(optarg); break; + case '4': + s.ai_family = AF_INET; + break; + case '6': + s.ai_family = AF_INET6; + break; case 'v': o.severity++; break; @@ -27,6 +27,12 @@ static int ssh_set_opts(ssh_session ssh, struct mscp_ssh_opts *opts) return -1; } + if (opts->ai_family && + ssh_options_set(ssh, SSH_OPTIONS_AI_FAMILY, &opts->ai_family) < 0) { + priv_set_errv("failed to set address family"); + return -1; + } + if (opts->identity && ssh_options_set(ssh, SSH_OPTIONS_IDENTITY, opts->identity) < 0) { priv_set_errv("failed to set identity"); diff --git a/test/test_e2e.py b/test/test_e2e.py index fa17a6a..bfe1c00 100644 --- a/test/test_e2e.py +++ b/test/test_e2e.py @@ -324,6 +324,38 @@ def test_set_port(mscp, src_prefix, dst_prefix, src, dst): run2ng([mscp, "-H", "-vvv", "-P", 21, src_prefix + src.path, dst_prefix + dst.path]) src.cleanup() +def test_v4only(mscp): + src = File("src", size = 1024).make() + dst = File("dst") + dst_prefix = "localhost:{}/".format(os.getcwd()) + run2ok([mscp, "-H", "-vvv", "-4", src.path, dst_prefix + dst.path]) + assert check_same_md5sum(src, dst) + src.cleanup() + dst.cleanup() + +def test_v6only(mscp): + src = File("src", size = 1024).make() + dst = File("dst") + dst_prefix = "localhost:{}/".format(os.getcwd()) + run2ok([mscp, "-H", "-vvv", "-6", src.path, dst_prefix + dst.path]) + assert check_same_md5sum(src, dst) + src.cleanup() + dst.cleanup() + +def test_v4_to_v6_should_fail(mscp): + src = File("src", size = 1024).make() + dst = File("dst") + dst_prefix = "[::1]:{}/".format(os.getcwd()) + run2ng([mscp, "-H", "-vvv", "-4", src.path, dst_prefix + dst.path]) + src.cleanup() + +def test_v6_to_v4_should_fail(mscp): + src = File("src", size = 1024).make() + dst = File("dst") + dst_prefix = "127.0.0.1:{}/".format(os.getcwd()) + run2ng([mscp, "-H", "-vvv", "-6", src.path, dst_prefix + dst.path]) + src.cleanup() + @pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix) def test_set_conn_interval(mscp, src_prefix, dst_prefix): srcs = [] @@ -442,3 +474,4 @@ def test_specify_invalid_password_via_env(mscp): run2ng([mscp, "-H", "-vvv", "-l", "test", src.path, "localhost:" + dst.path], env = env) src.cleanup() + |