From fe3c4e0a5313791b020176b2523534c560d7a114 Mon Sep 17 00:00:00 2001 From: Ryo Nakamura Date: Sat, 15 Nov 2025 20:20:54 +0900 Subject: [PATCH] add -B BIND_ADDR option This option enables using multiple NICs to transfer files. --- doc/mscp.1.in | 10 ++++++++++ doc/mscp.rst | 17 ++++++++++++----- include/mscp.h | 2 ++ src/main.c | 19 ++++++++++++++++--- src/ssh.c | 19 +++++++++++++++++++ 5 files changed, 59 insertions(+), 8 deletions(-) diff --git a/doc/mscp.1.in b/doc/mscp.1.in index 3c1802e..e04a7ac 100644 --- a/doc/mscp.1.in +++ b/doc/mscp.1.in @@ -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 NICs having differnt +IP addresses. + .TP .B \-4 Uses IPv4 addresses only. diff --git a/doc/mscp.rst b/doc/mscp.rst index 1f43fa9..f8d74f8 100644 --- a/doc/mscp.rst +++ b/doc/mscp.rst @@ -2,7 +2,7 @@ MSCP ==== -:Date: v0.2.4 +:Date: v0.2.4-2-g7b5a970 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 NICs having + differnt IP addresses. + **-4** Uses IPv4 addresses only. diff --git a/include/mscp.h b/include/mscp.h index 236f526..386f66a 100644 --- a/include/mscp.h +++ b/include/mscp.h @@ -30,6 +30,7 @@ #include #include + #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 */ diff --git a/src/main.c b/src/main.c index 0ad58f1..7232307 100644 --- a/src/main.c +++ b/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; diff --git a/src/ssh.c b/src/ssh.c index 106eee2..3138148 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -6,6 +6,7 @@ #include #include +#include #include #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");