mirror of
https://github.com/upa/mscp.git
synced 2026-02-06 04:24:45 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aba14b3fdc | ||
|
|
cd6136399e | ||
|
|
dfdf683828 | ||
|
|
979f110097 | ||
|
|
fe3c4e0a53 | ||
|
|
7b5a97092a |
@@ -41,6 +41,9 @@ mscp \- copy files over multiple SSH connections
|
||||
.BI \-L \ LIMIT_BITRATE\c
|
||||
]
|
||||
[\c
|
||||
.BI \-B \ BIND_ADDR\c
|
||||
]
|
||||
[\c
|
||||
.BI \-l \ LOGIN_NAME\c
|
||||
]
|
||||
[\c
|
||||
@@ -220,6 +223,13 @@ delivered over SSH. Changing this value is not recommended at present.
|
||||
Limits the bitrate, specified with k (K), m (M), and g (G), e.g., 100m
|
||||
indicates 100 Mbps.
|
||||
|
||||
.TP
|
||||
.B \-B \fIBIND_ADDR\fR
|
||||
Specifies a local IP address to bind. When multiple -B options are
|
||||
specified, SSH connections will be bound to the addresses in a round
|
||||
robin manner. This feature enables using multiple network interfaces
|
||||
having differnt IP addresses.
|
||||
|
||||
.TP
|
||||
.B \-4
|
||||
Uses IPv4 addresses only.
|
||||
@@ -435,6 +445,18 @@ will be faster than the default chacha20-poly1305 cipher, particularly
|
||||
on hosts that support AES-NI.
|
||||
|
||||
|
||||
.PP
|
||||
Use multiple network interfaces having different IP addresses:
|
||||
|
||||
.nf
|
||||
$ mscp -n 9 -B 10.0.0.10 -B 10.0.0.11 -B 10.0.0.12 many-large-files 192.168.0.1:dst/
|
||||
.fi
|
||||
|
||||
Out of the 9 SSH connections, three of them are bound to each of
|
||||
10.0.0.10, 10.0.0.11, and 10.0.0.12 in a round robin manner.
|
||||
|
||||
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR scp (1),
|
||||
.BR ssh (1),
|
||||
|
||||
26
doc/mscp.rst
26
doc/mscp.rst
@@ -2,7 +2,7 @@
|
||||
MSCP
|
||||
====
|
||||
|
||||
:Date: v0.2.4
|
||||
:Date: v0.2.4-6-gcd61363
|
||||
|
||||
NAME
|
||||
====
|
||||
@@ -16,10 +16,11 @@ SYNOPSIS
|
||||
*COREMASK* ] [ **-u** *MAX_STARTUPS* ] [ **-I** *INTERVAL* ] [ **-W**
|
||||
*CHECKPOINT* ] [ **-R** *CHECKPOINT* ] [ **-s** *MIN_CHUNK_SIZE* ] [
|
||||
**-S** *MAX_CHUNK_SIZE* ] [ **-a** *NR_AHEAD* ] [ **-b** *BUF_SIZE* ] [
|
||||
**-L** *LIMIT_BITRATE* ] [ **-l** *LOGIN_NAME* ] [ **-P** *PORT* ] [
|
||||
**-F** *SSH_CONFIG* ] [ **-o** *SSH_OPTION* ] [ **-i** *IDENTITY* ] [
|
||||
**-J** *DESTINATION* ] [ **-c** *CIPHER* ] [ **-M** *HMAC* ] [ **-C**
|
||||
*COMPRESS* ] [ **-g** *CONGESTION* ] *source ... target*
|
||||
**-L** *LIMIT_BITRATE* ] [ **-B** *BIND_ADDR* ] [ **-l** *LOGIN_NAME* ]
|
||||
[ **-P** *PORT* ] [ **-F** *SSH_CONFIG* ] [ **-o** *SSH_OPTION* ] [
|
||||
**-i** *IDENTITY* ] [ **-J** *DESTINATION* ] [ **-c** *CIPHER* ] [
|
||||
**-M** *HMAC* ] [ **-C** *COMPRESS* ] [ **-g** *CONGESTION* ] *source
|
||||
... target*
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
@@ -120,6 +121,12 @@ OPTIONS
|
||||
Limits the bitrate, specified with k (K), m (M), and g (G), e.g.,
|
||||
100m indicates 100 Mbps.
|
||||
|
||||
**-B BIND_ADDR**
|
||||
Specifies a local IP address to bind. When multiple -B options are
|
||||
specified, SSH connections will be bound to the addresses in a round
|
||||
robin manner. This feature enables using multiple network interfaces
|
||||
having differnt IP addresses.
|
||||
|
||||
**-4**
|
||||
Uses IPv4 addresses only.
|
||||
|
||||
@@ -269,6 +276,15 @@ SFTP WRITE/READ commands, and **-c aes128-gcm@openssh.com** will be
|
||||
faster than the default chacha20-poly1305 cipher, particularly on hosts
|
||||
that support AES-NI.
|
||||
|
||||
Use multiple network interfaces having different IP addresses:
|
||||
|
||||
::
|
||||
|
||||
$ mscp -n 9 -B 10.0.0.10 -B 10.0.0.11 -B 10.0.0.12 many-large-files 192.168.0.1:dst/
|
||||
|
||||
Out of the 9 SSH connections, three of them are bound to each of
|
||||
10.0.0.10, 10.0.0.11, and 10.0.0.12 in a round robin manner.
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
#define MSCP_DIRECTION_L2R 1 /** Indicates local to remote copy */
|
||||
#define MSCP_DIRECTION_R2L 2 /** Indicates remote to local copy */
|
||||
|
||||
@@ -60,6 +61,7 @@ struct mscp_ssh_opts {
|
||||
/* ssh options */
|
||||
char *login_name; /** ssh username */
|
||||
char *port; /** ssh port */
|
||||
char **bind_addrs; /** addresses to bind, terminated by NULL */
|
||||
int ai_family; /** address family */
|
||||
char *config; /** path to ssh_config, default ~/.ssh/config*/
|
||||
char **options; /** array of ssh_config options, terminated by NULL */
|
||||
|
||||
19
src/main.c
19
src/main.c
@@ -28,7 +28,7 @@ void usage(bool print_help)
|
||||
"Usage: mscp [-46vqDpdNh] [-n nr_conns] [-m coremask] [-u max_startups]\n"
|
||||
" [-I interval] [-W checkpoint] [-R checkpoint]\n"
|
||||
" [-s min_chunk_sz] [-S max_chunk_sz] [-a nr_ahead]\n"
|
||||
" [-b buf_sz] [-L limit_bitrate]\n"
|
||||
" [-b buf_sz] [-L limit_bitrate] [-B bind_addr]\n"
|
||||
" [-l login_name] [-P port] [-F ssh_config] [-o ssh_option]\n"
|
||||
" [-i identity_file] [-J destination] [-c cipher_spec] [-M hmac_spec]\n"
|
||||
" [-C compress] [-g congestion]\n"
|
||||
@@ -51,7 +51,8 @@ void usage(bool print_help)
|
||||
" -S MAX_CHUNK_SIZE max chunk size (default: filesize/nr_conn/4)\n"
|
||||
" -a NR_AHEAD number of inflight SFTP commands (default: 32)\n"
|
||||
" -b BUF_SZ buffer size for i/o and transfer\n"
|
||||
" -L LIMIT_BITRATE Limit the bitrate, n[KMG] (default: 0, no limit)\n"
|
||||
" -L LIMIT_BITRATE limit the bitrate, n[KMG] (default: 0, no limit)\n"
|
||||
" -B BIND_ADDR bind address (accept multiple times)\n"
|
||||
"\n"
|
||||
" -4 use IPv4\n"
|
||||
" -6 use IPv6\n"
|
||||
@@ -360,13 +361,14 @@ int main(int argc, char **argv)
|
||||
int direction = 0;
|
||||
char *remote = NULL, *checkpoint_save = NULL, *checkpoint_load = NULL;
|
||||
bool quiet = false, dryrun = false, resume = false;
|
||||
int nr_baddrs = 0;
|
||||
int nr_options = 0;
|
||||
|
||||
memset(&s, 0, sizeof(s));
|
||||
memset(&o, 0, sizeof(o));
|
||||
o.severity = MSCP_SEVERITY_WARN;
|
||||
|
||||
#define mscpopts "n:m:u:I:W:R:s:S:a:b:L:46vqDrl:P:F:o:i:J:c:M:C:g:pdNh"
|
||||
#define mscpopts "n:m:u:I:W:R:s:S:a:b:L:B:46vqDrl:P:F:o:i:J:c:M:C:g:pdNh"
|
||||
while ((ch = getopt(argc, argv, mscpopts)) != -1) {
|
||||
switch (ch) {
|
||||
case 'n':
|
||||
@@ -407,6 +409,17 @@ int main(int argc, char **argv)
|
||||
case 'L':
|
||||
o.bitrate = atol_with_unit(optarg, false);
|
||||
break;
|
||||
case 'B':
|
||||
nr_baddrs++;
|
||||
s.bind_addrs = realloc(s.bind_addrs,
|
||||
sizeof(char *) * (nr_baddrs + 1));
|
||||
if (!s.bind_addrs) {
|
||||
pr_err("realloc: %s", strerrno());
|
||||
return 1;
|
||||
}
|
||||
s.bind_addrs[nr_baddrs - 1] = optarg;
|
||||
s.bind_addrs[nr_baddrs] = NULL;
|
||||
break;
|
||||
case '4':
|
||||
s.ai_family = AF_INET;
|
||||
break;
|
||||
|
||||
43
src/ssh.c
43
src/ssh.c
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <ssh.h>
|
||||
#include <mscp.h>
|
||||
#include <atomic.h>
|
||||
#include <strerrno.h>
|
||||
|
||||
#include "libssh/callbacks.h"
|
||||
@@ -14,6 +15,9 @@
|
||||
static int ssh_verify_known_hosts(ssh_session session);
|
||||
static int ssh_authenticate_kbdint(ssh_session session);
|
||||
|
||||
static lock bind_addr_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int bind_addr_idx;
|
||||
|
||||
static int ssh_set_opts(ssh_session ssh, struct mscp_ssh_opts *opts)
|
||||
{
|
||||
ssh_set_log_level(opts->debug_level);
|
||||
@@ -29,6 +33,21 @@ static int ssh_set_opts(ssh_session ssh, struct mscp_ssh_opts *opts)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opts->bind_addrs) {
|
||||
/* ssh_set_opts can be called from multiple threads,
|
||||
* thus protect bind_addr_idx with a lock. */
|
||||
char *bind_addr;
|
||||
LOCK_ACQUIRE(&bind_addr_lock);
|
||||
bind_addr = opts->bind_addrs[bind_addr_idx];
|
||||
bind_addr_idx = (opts->bind_addrs[bind_addr_idx + 1] == NULL ?
|
||||
0 : bind_addr_idx + 1);
|
||||
LOCK_RELEASE();
|
||||
if (ssh_options_set(ssh, SSH_OPTIONS_BINDADDR, bind_addr) < 0) {
|
||||
priv_set_errv("failed to set bind address %s", bind_addr);
|
||||
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");
|
||||
@@ -123,14 +142,21 @@ static int ssh_authenticate(ssh_session ssh, struct mscp_ssh_opts *opts)
|
||||
if (ssh_userauth_none(ssh, NULL) == SSH_AUTH_SUCCESS)
|
||||
return 0;
|
||||
|
||||
/* save auth_bit_mask for further authentications */
|
||||
/* save auth_bit_mask for further authentications.
|
||||
* when an authentication succeeds, auth_bit_mask is
|
||||
* overwritten with the suceeded authentication method
|
||||
* to avoid authentication failures by other methods.
|
||||
*/
|
||||
auth_bit_mask = ssh_userauth_list(ssh, NULL);
|
||||
}
|
||||
|
||||
if (auth_bit_mask & SSH_AUTH_METHOD_PUBLICKEY) {
|
||||
char *p = opts->passphrase ? opts->passphrase : NULL;
|
||||
if (ssh_userauth_publickey_auto(ssh, NULL, p) == SSH_AUTH_SUCCESS)
|
||||
if (ssh_userauth_publickey_auto(ssh, NULL, p) == SSH_AUTH_SUCCESS) {
|
||||
auth_bit_mask = SSH_AUTH_METHOD_PUBLICKEY;
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (auth_bit_mask & SSH_AUTH_METHOD_PASSWORD) {
|
||||
@@ -146,14 +172,17 @@ static int ssh_authenticate(ssh_session ssh, struct mscp_ssh_opts *opts)
|
||||
}
|
||||
}
|
||||
|
||||
if (ssh_userauth_password(ssh, NULL, opts->password) == SSH_AUTH_SUCCESS)
|
||||
return 0;
|
||||
if (ssh_userauth_password(ssh, NULL, opts->password) == SSH_AUTH_SUCCESS) {
|
||||
auth_bit_mask = SSH_AUTH_METHOD_PASSWORD;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
auth_bit_mask = ssh_userauth_list(ssh, NULL);
|
||||
if (auth_bit_mask & SSH_AUTH_METHOD_INTERACTIVE) {
|
||||
if (ssh_authenticate_kbdint(ssh) == SSH_AUTH_SUCCESS)
|
||||
return 0;
|
||||
if (ssh_authenticate_kbdint(ssh) == SSH_AUTH_SUCCESS) {
|
||||
auth_bit_mask = SSH_AUTH_METHOD_INTERACTIVE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
@@ -12,24 +12,27 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from subprocess import check_call, CalledProcessError
|
||||
from subprocess import check_output, CalledProcessError, STDOUT
|
||||
from util import File, check_same_md5sum
|
||||
|
||||
|
||||
def run2ok(args, env = None, quiet = False):
|
||||
def run2ok(args, env = None) -> str:
|
||||
cmd = list(map(str, args))
|
||||
if not quiet:
|
||||
print("cmd: {}".format(" ".join(cmd)))
|
||||
check_call(cmd, env = env)
|
||||
print("cmd: {}".format(" ".join(cmd)))
|
||||
out = check_output(cmd, env = env, stderr = STDOUT).decode()
|
||||
print(out)
|
||||
return out
|
||||
|
||||
def run2ng(args, env = None, timeout = None, quiet = False):
|
||||
def run2ng(args, env = None, timeout = None):
|
||||
if timeout:
|
||||
args = ["timeout", "-s", "INT", timeout] + args
|
||||
cmd = list(map(str, args))
|
||||
if not quiet:
|
||||
print("cmd: {}".format(" ".join(cmd)))
|
||||
with pytest.raises(CalledProcessError):
|
||||
check_call(cmd, env = env)
|
||||
print("cmd: {}".format(" ".join(cmd)))
|
||||
with pytest.raises(CalledProcessError) as execinfo:
|
||||
check_output(cmd, env = env, stderr = STDOUT)
|
||||
out = execinfo.value.stdout.decode()
|
||||
print(out)
|
||||
return out
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -423,6 +426,36 @@ def test_bwlimit(mscp, src_prefix, dst_prefix):
|
||||
assert end - start > 7
|
||||
|
||||
|
||||
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
|
||||
def test_bind_ng(mscp, src_prefix, dst_prefix):
|
||||
"""Bind to invalid address should fail."""
|
||||
out = run2ng([mscp, "-vvv", "-ddd", "-B", "192.168.10.10",
|
||||
src_prefix + "src", dst_prefix + "dst"])
|
||||
assert ("Cannot assign requested address" in out or
|
||||
"Address not available" in out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
|
||||
def test_bind_ok(mscp, src_prefix, dst_prefix):
|
||||
"""Bind to multiple lo addresses and check all of them used """
|
||||
|
||||
srcs = [ File(f"src-{x}", size = 128).make() for x in range(10) ]
|
||||
src_paths = [ src_prefix + src.path for src in srcs ]
|
||||
|
||||
out = run2ok([mscp, "-v", "-ddd",
|
||||
"-B", "127.0.0.10", "-B", "127.0.0.20", "-B", "127.0.0.30",
|
||||
] +
|
||||
src_paths + [dst_prefix + "dst/"])
|
||||
|
||||
for src in srcs:
|
||||
dst = File(f"dst/{src.path}")
|
||||
assert check_same_md5sum(src, dst)
|
||||
|
||||
assert "127.0.0.10" in out
|
||||
assert "127.0.0.20" in out
|
||||
assert "127.0.0.30" in out
|
||||
|
||||
|
||||
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
|
||||
@pytest.mark.parametrize("src, dst", param_single_copy)
|
||||
def test_set_port_ng(mscp, src_prefix, dst_prefix, src, dst):
|
||||
@@ -462,16 +495,13 @@ def test_v6_to_v4_should_fail(mscp):
|
||||
dst_prefix = "127.0.0.1:{}/".format(os.getcwd())
|
||||
run2ng([mscp, "-vvv", "-6", src.path, dst_prefix + dst.path])
|
||||
|
||||
def test_quiet_mode(capsys, mscp):
|
||||
def test_quiet_mode(mscp):
|
||||
src = File("src", size = 1024).make()
|
||||
dst = File("dst")
|
||||
dst_prefix = "127.0.0.1:{}/".format(os.getcwd())
|
||||
run2ok([mscp, "-vvv", "-q", src.path, dst_prefix + dst.path], quiet=True)
|
||||
out = run2ok([mscp, "-vvv", "-q", src.path, dst_prefix + dst.path])
|
||||
assert check_same_md5sum(src, dst)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert not captured.out
|
||||
assert not captured.err
|
||||
assert not out
|
||||
|
||||
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
|
||||
def test_set_conn_interval(mscp, src_prefix, dst_prefix):
|
||||
|
||||
Reference in New Issue
Block a user