add -4 and -6 options to use either IPv4 or IPv6

This commit is contained in:
Ryo Nakamura
2024-02-07 14:34:15 +09:00
parent a9c59f744a
commit b2628b54fb
8 changed files with 174 additions and 20 deletions

View File

@@ -6,7 +6,7 @@ mscp \- copy files over multiple SSH connections
.SH SYNOPSIS .SH SYNOPSIS
.B mscp .B mscp
.RB [ \-vqDpHdNh ] .RB [ \-46vqDpHdNh ]
[\c [\c
.BI \-n \ NR_CONNECTIONS\c .BI \-n \ NR_CONNECTIONS\c
] ]
@@ -150,6 +150,14 @@ Specifies the buffer size for I/O and transfer over SFTP. The default
value is 16384. Note that the SSH specification restricts buffer size value is 16384. Note that the SSH specification restricts buffer size
delivered over SSH. Changing this value is not recommended at present. 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 .TP
.B \-v .B \-v
Increments the verbose output level. Increments the verbose output level.

View File

@@ -2,7 +2,7 @@
MSCP MSCP
==== ====
:Date: v0.1.3-22-g9608400 :Date: v0.1.3-23-ga9c59f7
NAME NAME
==== ====
@@ -12,7 +12,7 @@ mscp - copy files over multiple SSH connections
SYNOPSIS SYNOPSIS
======== ========
**mscp** [**-vqDpHdNh**] [ **-n**\ *NR_CONNECTIONS* ] [ **mscp** [**-46vqDpHdNh**] [ **-n**\ *NR_CONNECTIONS* ] [
**-m**\ *COREMASK* ] [ **-u**\ *MAX_STARTUPS* ] [ **-I**\ *INTERVAL* ] [ **-m**\ *COREMASK* ] [ **-u**\ *MAX_STARTUPS* ] [ **-I**\ *INTERVAL* ] [
**-s**\ *MIN_CHUNK_SIZE* ] [ **-S**\ *MAX_CHUNK_SIZE* ] [ **-s**\ *MIN_CHUNK_SIZE* ] [ **-S**\ *MAX_CHUNK_SIZE* ] [
**-a**\ *NR_AHEAD* ] [ **-b**\ *BUF_SIZE* ] [ **-l**\ *LOGIN_NAME* ] [ **-a**\ *NR_AHEAD* ] [ **-b**\ *BUF_SIZE* ] [ **-l**\ *LOGIN_NAME* ] [
@@ -87,6 +87,12 @@ OPTIONS
delivered over SSH. Changing this value is not recommended at delivered over SSH. Changing this value is not recommended at
present. present.
**-4**
Uses IPv4 addresses only.
**-6**
Uses IPv6 addresses only.
**-v** **-v**
Increments the verbose output level. Increments the verbose output level.

View File

@@ -58,6 +58,7 @@ struct mscp_ssh_opts {
/* ssh options */ /* ssh options */
char *login_name; /** ssh username */ char *login_name; /** ssh username */
char *port; /** ssh port */ char *port; /** ssh port */
int ai_family; /** address family */
char *config; /** path to ssh_config, default ~/.ssh/config*/ char *config; /** path to ssh_config, default ~/.ssh/config*/
char *identity; /** path to private key */ char *identity; /** path to private key */
char *cipher; /** cipher spec */ char *cipher; /** cipher spec */

View File

@@ -37,10 +37,18 @@ index 1fce7b76..b64d1455 100644
int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len); 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 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 --- a/include/libssh/libssh.h
+++ b/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_GSSAPI_AUTH,
SSH_OPTIONS_GLOBAL_KNOWNHOSTS, SSH_OPTIONS_GLOBAL_KNOWNHOSTS,
SSH_OPTIONS_NODELAY, SSH_OPTIONS_NODELAY,
@@ -48,7 +56,7 @@ index 669a0a96..b6a93ac7 100644
SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES,
SSH_OPTIONS_PROCESS_CONFIG, SSH_OPTIONS_PROCESS_CONFIG,
SSH_OPTIONS_REKEY_DATA, 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 const char* ssh_get_hmac_out(ssh_session session);
LIBSSH_API ssh_buffer ssh_buffer_new(void); 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); LIBSSH_API void ssh_buffer_free(ssh_buffer buffer);
#define SSH_BUFFER_FREE(x) \ #define SSH_BUFFER_FREE(x) \
do { if ((x) != NULL) { ssh_buffer_free(x); x = NULL; } } while(0) 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 uint32_t ssh_buffer_get_len(ssh_buffer buffer);
LIBSSH_API int ssh_session_set_disconnect_message(ssh_session session, const char *message); 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" #include "libssh/legacy.h"
#endif #endif
diff --git a/include/libssh/session.h b/include/libssh/session.h 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 --- a/include/libssh/session.h
+++ b/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 flags;
int exp_flags; int exp_flags;
int nodelay; int nodelay;
@@ -204,9 +220,27 @@ index 8991e006..e0414801 100644
* @brief Ensure the buffer has at least a certain preallocated size. * @brief Ensure the buffer has at least a certain preallocated size.
* *
diff --git a/src/connect.c b/src/connect.c diff --git a/src/connect.c b/src/connect.c
index 15cae644..e7520f40 100644 index 15cae644..02ef43b4 100644
--- a/src/connect.c --- a/src/connect.c
+++ b/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) @@ -156,6 +156,20 @@ static int set_tcp_nodelay(socket_t socket)
sizeof(opt)); sizeof(opt));
} }
@@ -228,6 +262,24 @@ index 15cae644..e7520f40 100644
/** /**
* @internal * @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, @@ -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); rc = connect(s, itr->ai_addr, itr->ai_addrlen);
if (rc == -1 && (errno != 0) && (errno != EINPROGRESS)) { if (rc == -1 && (errno != 0) && (errno != EINPROGRESS)) {
diff --git a/src/options.c b/src/options.c diff --git a/src/options.c b/src/options.c
index b3ecffe1..fb966fa1 100644 index b3ecffe1..8de24ed6 100644
--- a/src/options.c --- a/src/options.c
+++ b/src/options.c +++ b/src/options.c
@@ -217,6 +217,7 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) @@ -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->opts.config_processed = src->opts.config_processed;
new->common.log_verbosity = src->common.log_verbosity; new->common.log_verbosity = src->common.log_verbosity;
new->common.callbacks = src->common.callbacks; 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 * Set it to disable Nagle's Algorithm (TCP_NODELAY) on the
* session socket. (int, 0=false) * session socket. (int, 0=false)
* *
@@ -270,7 +332,29 @@ index b3ecffe1..fb966fa1 100644
* - SSH_OPTIONS_PROCESS_CONFIG * - SSH_OPTIONS_PROCESS_CONFIG
* Set it to false to disable automatic processing of per-user * Set it to false to disable automatic processing of per-user
* and system-wide OpenSSH configuration files. LibSSH * 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; session->opts.nodelay = (*x & 0xff) > 0 ? 1 : 0;
} }
break; break;
@@ -292,10 +376,14 @@ index b3ecffe1..fb966fa1 100644
if (value == NULL) { if (value == NULL) {
ssh_set_error_invalid(session); ssh_set_error_invalid(session);
diff --git a/src/session.c b/src/session.c diff --git a/src/session.c b/src/session.c
index 8c509699..88602b6a 100644 index 8c509699..307388e5 100644
--- a/src/session.c --- a/src/session.c
+++ b/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.fd = -1;
session->opts.compressionlevel = 7; session->opts.compressionlevel = 7;
session->opts.nodelay = 0; session->opts.nodelay = 0;

View File

@@ -15,6 +15,8 @@ if [ ! -e /var/run/sshd.pid ]; then
fi fi
ssh-keyscan localhost >> ${HOME}/.ssh/known_hosts 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 # Run test
python3 -m pytest ../test -v python3 -m pytest ../test -v

View File

@@ -9,6 +9,8 @@
#include <sys/time.h> #include <sys/time.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <poll.h> #include <poll.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <pthread.h> #include <pthread.h>
#include <mscp.h> #include <mscp.h>
@@ -20,12 +22,12 @@
void usage(bool print_help) 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" "\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" " [-u max_startups] [-I interval]\n"
" [-s min_chunk_sz] [-S max_chunk_sz] [-a nr_ahead] [-b buf_sz]\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" " [-c cipher_spec] [-M hmac_spec] [-C compress] [-g congestion]\n"
" source ... target\n" " source ... target\n"
"\n"); "\n");
@@ -45,6 +47,8 @@ void usage(bool print_help)
" -a NR_AHEAD number of inflight SFTP commands (default: 32)\n" " -a NR_AHEAD number of inflight SFTP commands (default: 32)\n"
" -b BUF_SZ buffer size for i/o and transfer\n" " -b BUF_SZ buffer size for i/o and transfer\n"
"\n" "\n"
" -4 use IPv4\n"
" -6 use IPv6\n"
" -v increment verbose output level\n" " -v increment verbose output level\n"
" -q disable output\n" " -q disable output\n"
" -D dry run. check copy destinations with -vvv\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)); memset(&o, 0, sizeof(o));
o.severity = MSCP_SEVERITY_WARN; 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")) != #define mscpopts "n:m:u:I:s:S:a:b:46vqDrl:P:i:F:c:M:C:g:pHdNh"
-1) { while ((ch = getopt(argc, argv, mscpopts)) != -1) {
switch (ch) { switch (ch) {
case 'n': case 'n':
o.nr_threads = atoi(optarg); o.nr_threads = atoi(optarg);
@@ -294,6 +298,12 @@ int main(int argc, char **argv)
case 'b': case 'b':
o.buf_sz = atoi(optarg); o.buf_sz = atoi(optarg);
break; break;
case '4':
s.ai_family = AF_INET;
break;
case '6':
s.ai_family = AF_INET6;
break;
case 'v': case 'v':
o.severity++; o.severity++;
break; break;

View File

@@ -27,6 +27,12 @@ static int ssh_set_opts(ssh_session ssh, struct mscp_ssh_opts *opts)
return -1; 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 && if (opts->identity &&
ssh_options_set(ssh, SSH_OPTIONS_IDENTITY, opts->identity) < 0) { ssh_options_set(ssh, SSH_OPTIONS_IDENTITY, opts->identity) < 0) {
priv_set_errv("failed to set identity"); priv_set_errv("failed to set identity");

View File

@@ -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]) run2ng([mscp, "-H", "-vvv", "-P", 21, src_prefix + src.path, dst_prefix + dst.path])
src.cleanup() 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) @pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_set_conn_interval(mscp, src_prefix, dst_prefix): def test_set_conn_interval(mscp, src_prefix, dst_prefix):
srcs = [] srcs = []
@@ -442,3 +474,4 @@ def test_specify_invalid_password_via_env(mscp):
run2ng([mscp, "-H", "-vvv", "-l", "test", run2ng([mscp, "-H", "-vvv", "-l", "test",
src.path, "localhost:" + dst.path], env = env) src.path, "localhost:" + dst.path], env = env)
src.cleanup() src.cleanup()