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
.B mscp
.RB [ \-vqDpHdNh ]
.RB [ \-46vqDpHdNh ]
[\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
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.

View File

@@ -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.

View File

@@ -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 */

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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");

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])
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()