Merge pull request #3061 from shadowsocks/feature/redesigned-cli

Redesign CLI flags, help, and argument validation
This commit is contained in:
Max Lv
2026-09-12 21:25:11 +08:00
committed by GitHub
17 changed files with 1166 additions and 244 deletions
+4
View File
@@ -16,6 +16,10 @@ Current version: 3.3.6 | [Changelog](debian/changelog)
[CLI reference and configuration guide](https://shadowsocks.github.io/shadowsocks-c/)
are generated from the source with Doxygen and published after updates to `master`.
Use `--help` for grouped CLI options and `--version` for version information.
See [CLI conventions](https://shadowsocks.github.io/shadowsocks-c/index.html#cli_conventions)
for the long flags, short aliases, and configuration precedence.
## Community
See the [contribution guide](CONTRIBUTION.md) for development setup, testing,
+31
View File
@@ -14,3 +14,34 @@ source documentation.
[Download the generated man pages](https://shadowsocks.github.io/shadowsocks-c/man-pages.tar.gz)
from the published site. The archive includes the six CLI manuals and both
overview lookup names.
\section cli_conventions CLI conventions
Use descriptive long flags or their existing short aliases. Connections use
different names for the remote server and the local listener:
- Clients: `--server HOST --server-port PORT` selects the remote Shadowsocks server.
- Listeners: `--listen-address ADDRESS --listen-port PORT` controls where a client
or server accepts connections. The manager uses `--manager-address` for its
control socket and configuration or its API for individual server ports.
- Tunnels: `--destination HOST:PORT` selects the forwarding destination.
- Configuration: `--config FILE`, `--cipher NAME`, and `--password SECRET` map to
`-c`, `-m`, and `-k`. AEAD-2022 keys can also be supplied with `--key BASE64`.
Explicit transport and address-family flags override the JSON configuration.
The last transport flag
(`--tcp-only`, `--udp`, or `--udp-only`) wins, as does the last address preference
(`--ipv4-first` or `--ipv6-first`). Address preferences do not disable either IP
family. Other options retain their existing semantics, including repeatable
server addresses and `ss-local --server-url` imports. Zero-valued resource
settings such as MTU and TCP buffer sizes retain their existing behavior: they
fall back to the configured value, or the system default when none is configured.
`--help` and `--version` write to standard output and exit successfully. Unknown
options, missing values, invalid numeric CLI values, and unexpected positional
arguments write usage errors to standard error and exit with status 2.
The formerly ignored `-l` flag in `ss-server` and `ss-manager` is now an error.
Use `ss-server --listen-port` instead; manager-controlled ports come from its
configuration or API. The removed one-time-authentication flag `-A` remains an
error. Use an AEAD cipher.
+1 -1
View File
@@ -29,7 +29,7 @@ ss-local(1) can be started from command line and run in foreground.
Here is an example:
```
# Start ss-local with given parameters
ss-local -s example.com -p 12345 -l 1080 -k foobar -m aes-256-cfb
ss-local --server example.com --server-port 12345 --listen-port 1080 --password foobar --cipher aes-256-gcm
```
\section ss_local_see_also SEE ALSO
+1 -1
View File
@@ -32,7 +32,7 @@ Check out the `SEE ALSO` section for the default path of config file.
```
# Start the ss-server
ss-server -c /etc/shadowsocks-libev/config.json
ss-server --config /etc/shadowsocks-libev/config.json
```
\section ss_server_incompatibility INCOMPATIBILITY
+1 -1
View File
@@ -31,7 +31,7 @@ through the shadowsocks tunnel. Here is an example:
```
# Forward local UDP port 5353 to 8.8.8.8:53 through the ss-server
ss-tunnel -s example.com -p 12345 -l 5353 -k foobar -m aes-256-cfb -L 8.8.8.8:53 -u
ss-tunnel --server example.com --server-port 12345 --listen-port 5353 --password foobar --cipher aes-256-gcm --destination 8.8.8.8:53 --udp
# Then configure your system to use 127.0.0.1:5353 as the DNS server
dig @127.0.0.1 -p 5353 www.google.com
+1 -1
View File
@@ -51,7 +51,7 @@ def options(source, shell=False):
if len(tables) != 1:
raise ValueError("Expected one long_options table")
table = re.sub(r'^\s*#.*$', '', tables[0], flags=re.M)
entry = re.compile(r'\{\s*"([a-z0-9-]+)"\s*,\s*(no_argument|required_argument)\s*,\s*NULL\s*,\s*GETOPT_VAL_[A-Z0-9_]+\s*\}\s*,', re.S)
entry = re.compile(r'\{\s*"([a-z0-9-]+)"\s*,\s*(no_argument|required_argument)\s*,\s*NULL\s*,\s*(?:GETOPT_VAL_[A-Z0-9_]+|\x27[A-Za-z0-9]\x27)\s*\}\s*,', re.S)
for match in entry.finditer(table):
flag, argument = match.groups()
if '--' + flag in result:
+4 -1
View File
@@ -76,7 +76,10 @@ enum {
GETOPT_VAL_TCP_OUTGOING_SNDBUF,
GETOPT_VAL_TCP_OUTGOING_RCVBUF,
GETOPT_VAL_NFTABLES_SETS,
GETOPT_VAL_SERVER_URL
GETOPT_VAL_SERVER_URL,
GETOPT_VAL_VERSION,
GETOPT_VAL_TCP_ONLY,
GETOPT_VAL_IPV4_FIRST
};
#endif // _COMMON_H
+179 -18
View File
@@ -1512,6 +1512,91 @@ main(int argc, char **argv)
memset(remote_addr, 0, sizeof(ss_addr_t) * MAX_REMOTE_NUM);
/* [cli_long_config]
\par `--config <config_file>`
Read JSON configuration. See the CLI conventions for precedence and default-value behavior. Short alias: `-c`.
[cli_long_config] */
/* [cli_long_cipher]
\par `--cipher <encrypt_method>`
Select the encryption cipher. See `-m` for supported cipher names and key requirements. Short alias: `-m`.
[cli_long_cipher] */
/* [cli_long_timeout]
\par `--timeout <timeout>`
Same behavior as `-t`; see that option for details. Short alias: `-t`.
[cli_long_timeout] */
/* [cli_long_user]
\par `--user <user_name>`
Same behavior as `-a`; see that option for details. Short alias: `-a`.
[cli_long_user] */
/* [cli_long_pid_file]
\par `--pid-file <pid_file>`
Same behavior as `-f`; see that option for details. Short alias: `-f`.
[cli_long_pid_file] */
/* [cli_long_nofile]
\par `--nofile <number>`
Same behavior as `-n`; see that option for details. Short alias: `-n`.
[cli_long_nofile] */
/* [cli_long_udp]
\par `--udp`
Enable both TCP and UDP relay, overriding the configured mode. Short alias: `-u`.
[cli_long_udp] */
/* [cli_long_udp_only]
\par `--udp-only`
Enable UDP relay only, overriding the configured mode. Short alias: `-U`.
[cli_long_udp_only] */
/* [cli_long_ipv6_first]
\par `--ipv6-first`
Prefer IPv6 DNS results; overrides configuration. This does not restrict connections to IPv6. Short alias: `-6`.
[cli_long_ipv6_first] */
/* [cli_long_verbose]
\par `--verbose`
Same behavior as `-v`; see that option for details. Short alias: `-v`.
[cli_long_verbose] */
/* [cli_long_server]
\par `--server <server_host>`
Set a remote Shadowsocks server hostname or IP address; may be repeated. Short alias: `-s`.
[cli_long_server] */
/* [cli_long_server_port]
\par `--server-port <server_port>`
Set the remote Shadowsocks server port. Short alias: `-p`.
[cli_long_server_port] */
/* [cli_long_listen_address]
\par `--listen-address <local_address>`
Set the listening address. Server and manager addresses may be repeated. Short alias: `-b`.
[cli_long_listen_address] */
/* [cli_long_listen_port]
\par `--listen-port <local_port>`
Set the listening port. Short alias: `-l`.
[cli_long_listen_port] */
/* [cli_long_interface]
\par `--interface <interface>`
Same behavior as `-i`; see that option for details. Short alias: `-i`.
[cli_long_interface] */
/* [cli_long_vpn]
\par `--vpn`
Same behavior as `-V`; see that option for details. Short alias: `-V`.
[cli_long_vpn] */
/* [cli_long_stat_path]
\par `--stat-path <path>`
Same behavior as `-S`; see that option for details. Short alias: `-S`.
[cli_long_stat_path] */
/* [cli-options]
\snippet{doc} utils.c cli_short_f
\snippet{doc} utils.c cli_short_s
@@ -1533,6 +1618,26 @@ main(int argc, char **argv)
\snippet{doc} utils.c cli_short_V
\snippet{doc} utils.c cli_short_6
\snippet{doc} utils.c cli_short_A
\snippet{doc} local.c cli_long_config
\snippet{doc} local.c cli_long_cipher
\snippet{doc} local.c cli_long_timeout
\snippet{doc} local.c cli_long_user
\snippet{doc} local.c cli_long_pid_file
\snippet{doc} local.c cli_long_nofile
\snippet{doc} local.c cli_long_udp
\snippet{doc} local.c cli_long_udp_only
\snippet{doc} local.c cli_long_ipv6_first
\snippet{doc} local.c cli_long_verbose
\snippet{doc} local.c cli_long_server
\snippet{doc} local.c cli_long_server_port
\snippet{doc} local.c cli_long_listen_address
\snippet{doc} local.c cli_long_listen_port
\snippet{doc} local.c cli_long_interface
\snippet{doc} utils.c cli_long_version
\snippet{doc} utils.c cli_long_tcp_only
\snippet{doc} utils.c cli_long_ipv4_first
\snippet{doc} local.c cli_long_vpn
\snippet{doc} local.c cli_long_stat_path
\snippet{doc} utils.c cli_long_reuse_port
\snippet{doc} utils.c cli_long_tcp_incoming_sndbuf
\snippet{doc} utils.c cli_long_tcp_incoming_rcvbuf
@@ -1551,6 +1656,28 @@ main(int argc, char **argv)
\snippet{doc} utils.c cli_long_help
[cli-options] */
static struct option long_options[] = {
{ "config", required_argument, NULL, 'c' },
{ "cipher", required_argument, NULL, 'm' },
{ "timeout", required_argument, NULL, 't' },
{ "user", required_argument, NULL, 'a' },
{ "pid-file", required_argument, NULL, 'f' },
{ "nofile", required_argument, NULL, 'n' },
{ "udp", no_argument, NULL, 'u' },
{ "udp-only", no_argument, NULL, 'U' },
{ "ipv6-first", no_argument, NULL, '6' },
{ "verbose", no_argument, NULL, 'v' },
{ "server", required_argument, NULL, 's' },
{ "server-port", required_argument, NULL, 'p' },
{ "listen-address", required_argument, NULL, 'b' },
{ "listen-port", required_argument, NULL, 'l' },
{ "interface", required_argument, NULL, 'i' },
{ "version", no_argument, NULL, GETOPT_VAL_VERSION },
{ "tcp-only", no_argument, NULL, GETOPT_VAL_TCP_ONLY },
{ "ipv4-first", no_argument, NULL, GETOPT_VAL_IPV4_FIRST },
#ifdef __ANDROID__
{ "vpn", no_argument, NULL, 'V' },
{ "stat-path", required_argument, NULL, 'S' },
#endif
{ "reuse-port", no_argument, NULL, GETOPT_VAL_REUSE_PORT },
{ "tcp-incoming-sndbuf", required_argument, NULL, GETOPT_VAL_TCP_INCOMING_SNDBUF },
{ "tcp-incoming-rcvbuf", required_argument, NULL, GETOPT_VAL_TCP_INCOMING_RCVBUF },
@@ -1570,18 +1697,31 @@ main(int argc, char **argv)
{ NULL, 0, NULL, 0 }
};
int mode_set = 0;
int ipv6first_set = 0;
opterr = 0;
USE_TTY();
#ifdef __ANDROID__
while ((c = getopt_long(argc, argv, "f:s:p:l:k:t:m:i:c:b:a:n:S:huUvV6A",
while ((c = getopt_long(argc, argv, ":f:s:p:l:k:t:m:i:c:b:a:n:S:huUvV6A",
long_options, NULL)) != -1) {
#else
while ((c = getopt_long(argc, argv, "f:s:p:l:k:t:m:i:c:b:a:n:huUv6A",
while ((c = getopt_long(argc, argv, ":f:s:p:l:k:t:m:i:c:b:a:n:huUv6A",
long_options, NULL)) != -1) {
#endif
switch (c) {
case GETOPT_VAL_VERSION:
cli_version();
exit(EXIT_SUCCESS);
case GETOPT_VAL_TCP_ONLY:
mode = TCP_ONLY;
mode_set = 1;
break;
case GETOPT_VAL_IPV4_FIRST:
ipv6first = 0;
ipv6first_set = 1;
break;
case GETOPT_VAL_FAST_OPEN:
fast_open = 1;
break;
@@ -1591,7 +1731,7 @@ main(int argc, char **argv)
break;
case GETOPT_VAL_MTU:
if (ss_parse_int(optarg, 0, INT_MAX, &mtu) == -1) {
FATAL("invalid MTU");
cli_error("invalid MTU", c, NULL);
}
LOGI("set MTU to %d", mtu);
break;
@@ -1620,7 +1760,7 @@ main(int argc, char **argv)
* since they are applied as getopt reaches them.
*/
if (ss_url_parse(optarg, &server_url) != 0) {
FATAL("invalid ss:// server URL");
cli_error("invalid ss:// server URL", c, NULL);
}
if (remote_num < MAX_REMOTE_NUM) {
remote_addr[remote_num].host = server_url.host;
@@ -1642,22 +1782,22 @@ main(int argc, char **argv)
break;
case GETOPT_VAL_TCP_INCOMING_SNDBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_incoming_sndbuf) == -1) {
FATAL("invalid TCP incoming send buffer size");
cli_error("invalid TCP incoming send buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_INCOMING_RCVBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_incoming_rcvbuf) == -1) {
FATAL("invalid TCP incoming receive buffer size");
cli_error("invalid TCP incoming receive buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_OUTGOING_SNDBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_outgoing_sndbuf) == -1) {
FATAL("invalid TCP outgoing send buffer size");
cli_error("invalid TCP outgoing send buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_OUTGOING_RCVBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_outgoing_rcvbuf) == -1) {
FATAL("invalid TCP outgoing receive buffer size");
cli_error("invalid TCP outgoing receive buffer size", c, NULL);
}
break;
case 's':
@@ -1666,9 +1806,19 @@ main(int argc, char **argv)
}
break;
case 'p':
{
uint16_t checked_port;
if (ss_parse_uint16_port(optarg, &checked_port) != 0)
cli_error("port must be an integer from 1 to 65535", c, NULL);
}
remote_port = optarg;
break;
case 'l':
{
uint16_t checked_port;
if (ss_parse_uint16_port(optarg, &checked_port) != 0)
cli_error("port must be an integer from 1 to 65535", c, NULL);
}
local_port = optarg;
break;
case GETOPT_VAL_PASSWORD:
@@ -1680,6 +1830,11 @@ main(int argc, char **argv)
pid_path = optarg;
break;
case 't':
{
int checked_timeout;
if (ss_parse_int(optarg, 1, INT_MAX, &checked_timeout) != 0)
cli_error("timeout must be a positive integer", c, NULL);
}
timeout = optarg;
break;
case 'm':
@@ -1700,14 +1855,16 @@ main(int argc, char **argv)
#ifdef HAVE_SETRLIMIT
case 'n':
if (ss_parse_int(optarg, 0, INT_MAX, &nofile) == -1) {
FATAL("invalid nofile");
cli_error("invalid nofile", c, NULL);
}
break;
#endif
case 'u':
mode_set = 1;
mode = TCP_AND_UDP;
break;
case 'U':
mode_set = 1;
mode = UDP_ONLY;
break;
case 'v':
@@ -1718,6 +1875,7 @@ main(int argc, char **argv)
usage();
exit(EXIT_SUCCESS);
case '6':
ipv6first_set = 1;
ipv6first = 1;
break;
#ifdef __ANDROID__
@@ -1729,19 +1887,22 @@ main(int argc, char **argv)
break;
#endif
case 'A':
FATAL("One time auth has been deprecated. Try AEAD ciphers instead.");
cli_error("one-time authentication was removed; use an AEAD cipher", 'A', NULL);
break;
case ':':
cli_error("missing required argument for option", optopt, argv[optind - 1]);
break;
case '?':
// The option character is not recognized.
LOGE("Unrecognized option: %s", optarg);
opterr = 1;
cli_error("unrecognized or invalid option", optopt, argv[optind - 1]);
break;
default:
cli_error("option is unsupported on this platform", c, NULL);
break;
}
}
if (opterr) {
usage();
exit(EXIT_FAILURE);
if (optind < argc) {
cli_error("unexpected positional argument", 0, NULL);
}
if (argc == 1) {
@@ -1804,7 +1965,7 @@ main(int argc, char **argv)
if (fast_open == 0) {
fast_open = conf->fast_open;
}
if (mode == TCP_ONLY) {
if (!mode_set) {
mode = conf->mode;
}
if (mtu == 0) {
@@ -1821,7 +1982,7 @@ main(int argc, char **argv)
nofile = conf->nofile;
}
#endif
if (ipv6first == 0) {
if (!ipv6first_set) {
ipv6first = conf->ipv6_first;
}
if (acl == 0 && conf->acl != NULL) {
+133 -18
View File
@@ -1276,20 +1276,79 @@ main(int argc, char **argv)
Set a server listening hostname or IP address. May be repeated.
[cli_short_s] */
/* [cli_short_l]
\par `-l <local_port>`
Accepted for compatibility but ignored by this program; it does not configure a local listener.
[cli_short_l] */
/* [cli_short_c]
\par `-c <config_file>`
Use a JSON configuration file. The "port_password" field can start multiple ss-server instances.
[cli_short_c] */
/* [cli_long_config]
\par `--config <config_file>`
Read JSON configuration. See the CLI conventions for precedence and default-value behavior. Short alias: `-c`.
[cli_long_config] */
/* [cli_long_cipher]
\par `--cipher <encrypt_method>`
Select the encryption cipher. See `-m` for supported cipher names and key requirements. Short alias: `-m`.
[cli_long_cipher] */
/* [cli_long_timeout]
\par `--timeout <timeout>`
Same behavior as `-t`; see that option for details. Short alias: `-t`.
[cli_long_timeout] */
/* [cli_long_user]
\par `--user <user_name>`
Same behavior as `-a`; see that option for details. Short alias: `-a`.
[cli_long_user] */
/* [cli_long_pid_file]
\par `--pid-file <pid_file>`
Same behavior as `-f`; see that option for details. Short alias: `-f`.
[cli_long_pid_file] */
/* [cli_long_nofile]
\par `--nofile <number>`
Same behavior as `-n`; see that option for details. Short alias: `-n`.
[cli_long_nofile] */
/* [cli_long_udp]
\par `--udp`
Enable both TCP and UDP relay, overriding the configured mode. Short alias: `-u`.
[cli_long_udp] */
/* [cli_long_udp_only]
\par `--udp-only`
Enable UDP relay only, overriding the configured mode. Short alias: `-U`.
[cli_long_udp_only] */
/* [cli_long_ipv6_first]
\par `--ipv6-first`
Prefer IPv6 DNS results; overrides configuration. This does not restrict connections to IPv6. Short alias: `-6`.
[cli_long_ipv6_first] */
/* [cli_long_verbose]
\par `--verbose`
Same behavior as `-v`; see that option for details. Short alias: `-v`.
[cli_long_verbose] */
/* [cli_long_listen_address]
\par `--listen-address <server_host>`
Set the listening address. Server and manager addresses may be repeated. Short alias: `-s`.
[cli_long_listen_address] */
/* [cli_long_interface]
\par `--interface <interface>`
Same behavior as `-i`; see that option for details. Short alias: `-i`.
[cli_long_interface] */
/* [cli_long_nameserver]
\par `--nameserver <addr>`
Same behavior as `-d`; see that option for details. Short alias: `-d`.
[cli_long_nameserver] */
/* [cli-options]
\snippet{doc} utils.c cli_short_f
\snippet{doc} manager.c cli_short_s
\snippet{doc} manager.c cli_short_l
\snippet{doc} utils.c cli_short_k
\snippet{doc} utils.c cli_short_t
\snippet{doc} utils.c cli_short_m
@@ -1305,6 +1364,22 @@ Use a JSON configuration file. The "port_password" field can start multiple ss-s
\snippet{doc} utils.c cli_short_U
\snippet{doc} utils.c cli_short_v
\snippet{doc} utils.c cli_short_A
\snippet{doc} manager.c cli_long_config
\snippet{doc} manager.c cli_long_cipher
\snippet{doc} manager.c cli_long_timeout
\snippet{doc} manager.c cli_long_user
\snippet{doc} manager.c cli_long_pid_file
\snippet{doc} manager.c cli_long_nofile
\snippet{doc} manager.c cli_long_udp
\snippet{doc} manager.c cli_long_udp_only
\snippet{doc} manager.c cli_long_ipv6_first
\snippet{doc} manager.c cli_long_verbose
\snippet{doc} manager.c cli_long_listen_address
\snippet{doc} manager.c cli_long_interface
\snippet{doc} manager.c cli_long_nameserver
\snippet{doc} utils.c cli_long_version
\snippet{doc} utils.c cli_long_tcp_only
\snippet{doc} utils.c cli_long_ipv4_first
\snippet{doc} utils.c cli_long_fast_open
\snippet{doc} utils.c cli_long_no_delay
\snippet{doc} utils.c cli_long_reuse_port
@@ -1319,6 +1394,22 @@ Use a JSON configuration file. The "port_password" field can start multiple ss-s
\snippet{doc} utils.c cli_long_help
[cli-options] */
static struct option long_options[] = {
{ "config", required_argument, NULL, 'c' },
{ "cipher", required_argument, NULL, 'm' },
{ "timeout", required_argument, NULL, 't' },
{ "user", required_argument, NULL, 'a' },
{ "pid-file", required_argument, NULL, 'f' },
{ "nofile", required_argument, NULL, 'n' },
{ "udp", no_argument, NULL, 'u' },
{ "udp-only", no_argument, NULL, 'U' },
{ "ipv6-first", no_argument, NULL, '6' },
{ "verbose", no_argument, NULL, 'v' },
{ "listen-address", required_argument, NULL, 's' },
{ "interface", required_argument, NULL, 'i' },
{ "nameserver", required_argument, NULL, 'd' },
{ "version", no_argument, NULL, GETOPT_VAL_VERSION },
{ "tcp-only", no_argument, NULL, GETOPT_VAL_TCP_ONLY },
{ "ipv4-first", no_argument, NULL, GETOPT_VAL_IPV4_FIRST },
{ "fast-open", no_argument, NULL, GETOPT_VAL_FAST_OPEN },
{ "no-delay", no_argument, NULL, GETOPT_VAL_NODELAY },
{ "reuse-port", no_argument, NULL, GETOPT_VAL_REUSE_PORT },
@@ -1336,13 +1427,26 @@ Use a JSON configuration file. The "port_password" field can start multiple ss-s
{ NULL, 0, NULL, 0 }
};
int mode_set = 0;
int ipv6first_set = 0;
opterr = 0;
USE_TTY();
while ((c = getopt_long(argc, argv, "f:s:l:k:t:m:c:i:d:a:n:D:6huUvA",
while ((c = getopt_long(argc, argv, ":f:s:k:t:m:c:i:d:a:n:D:6huUvA",
long_options, NULL)) != -1)
switch (c) {
case GETOPT_VAL_VERSION:
cli_version();
exit(EXIT_SUCCESS);
case GETOPT_VAL_TCP_ONLY:
mode = TCP_ONLY;
mode_set = 1;
break;
case GETOPT_VAL_IPV4_FIRST:
ipv6first = 0;
ipv6first_set = 1;
break;
case GETOPT_VAL_REUSE_PORT:
reuse_port = 1;
break;
@@ -1363,7 +1467,7 @@ Use a JSON configuration file. The "port_password" field can start multiple ss-s
break;
case GETOPT_VAL_MTU:
if (ss_parse_int(optarg, 0, INT_MAX, &mtu) == -1) {
FATAL("invalid MTU");
cli_error("invalid MTU", c, NULL);
}
break;
case GETOPT_VAL_PLUGIN:
@@ -1386,6 +1490,11 @@ Use a JSON configuration file. The "port_password" field can start multiple ss-s
pid_path = optarg;
break;
case 't':
{
int checked_timeout;
if (ss_parse_int(optarg, 1, INT_MAX, &checked_timeout) != 0)
cli_error("timeout must be a positive integer", c, NULL);
}
timeout = optarg;
break;
case 'm':
@@ -1404,12 +1513,15 @@ Use a JSON configuration file. The "port_password" field can start multiple ss-s
user = optarg;
break;
case 'u':
mode_set = 1;
mode = TCP_AND_UDP;
break;
case 'U':
mode_set = 1;
mode = UDP_ONLY;
break;
case '6':
ipv6first_set = 1;
ipv6first = 1;
break;
case GETOPT_VAL_WORKDIR:
@@ -1426,23 +1538,26 @@ Use a JSON configuration file. The "port_password" field can start multiple ss-s
#ifdef HAVE_SETRLIMIT
case 'n':
if (ss_parse_int(optarg, 0, INT_MAX, &nofile) == -1) {
FATAL("invalid nofile");
cli_error("invalid nofile", c, NULL);
}
break;
#endif
case 'A':
FATAL("One time auth has been deprecated. Try AEAD ciphers instead.");
cli_error("one-time authentication was removed; use an AEAD cipher", 'A', NULL);
break;
case ':':
cli_error("missing required argument for option", optopt, argv[optind - 1]);
break;
case '?':
// The option character is not recognized.
LOGE("Unrecognized option: %s", optarg);
opterr = 1;
cli_error("unrecognized or invalid option", optopt, argv[optind - 1]);
break;
default:
cli_error("option is unsupported on this platform", c, NULL);
break;
}
if (opterr) {
usage();
exit(EXIT_FAILURE);
if (optind < argc) {
cli_error("unexpected positional argument", 0, NULL);
}
if (conf_path != NULL) {
@@ -1476,7 +1591,7 @@ Use a JSON configuration file. The "port_password" field can start multiple ss-s
if (nameservers == NULL) {
nameservers = conf->nameserver;
}
if (mode == TCP_ONLY) {
if (!mode_set) {
mode = conf->mode;
}
if (mtu == 0) {
@@ -1488,7 +1603,7 @@ Use a JSON configuration file. The "port_password" field can start multiple ss-s
if (plugin_opts == NULL) {
plugin_opts = conf->plugin_opts;
}
if (ipv6first == 0) {
if (!ipv6first_set) {
ipv6first = conf->ipv6_first;
}
if (workdir == NULL) {
+161 -16
View File
@@ -927,6 +927,81 @@ main(int argc, char **argv)
Enable UDP relay. Requires Linux TPROXY support and permission to configure transparent proxying.
[cli_short_u] */
/* [cli_long_config]
\par `--config <config_file>`
Read JSON configuration. See the CLI conventions for precedence and default-value behavior. Short alias: `-c`.
[cli_long_config] */
/* [cli_long_cipher]
\par `--cipher <encrypt_method>`
Select the encryption cipher. See `-m` for supported cipher names and key requirements. Short alias: `-m`.
[cli_long_cipher] */
/* [cli_long_timeout]
\par `--timeout <timeout>`
Same behavior as `-t`; see that option for details. Short alias: `-t`.
[cli_long_timeout] */
/* [cli_long_user]
\par `--user <user_name>`
Same behavior as `-a`; see that option for details. Short alias: `-a`.
[cli_long_user] */
/* [cli_long_pid_file]
\par `--pid-file <pid_file>`
Same behavior as `-f`; see that option for details. Short alias: `-f`.
[cli_long_pid_file] */
/* [cli_long_nofile]
\par `--nofile <number>`
Same behavior as `-n`; see that option for details. Short alias: `-n`.
[cli_long_nofile] */
/* [cli_long_udp]
\par `--udp`
Enable both TCP and UDP relay, overriding the configured mode. Short alias: `-u`.
[cli_long_udp] */
/* [cli_long_udp_only]
\par `--udp-only`
Enable UDP relay only, overriding the configured mode. Short alias: `-U`.
[cli_long_udp_only] */
/* [cli_long_ipv6_first]
\par `--ipv6-first`
Prefer IPv6 DNS results; overrides configuration. This does not restrict connections to IPv6. Short alias: `-6`.
[cli_long_ipv6_first] */
/* [cli_long_verbose]
\par `--verbose`
Same behavior as `-v`; see that option for details. Short alias: `-v`.
[cli_long_verbose] */
/* [cli_long_server]
\par `--server <server_host>`
Set a remote Shadowsocks server hostname or IP address; may be repeated. Short alias: `-s`.
[cli_long_server] */
/* [cli_long_server_port]
\par `--server-port <server_port>`
Set the remote Shadowsocks server port. Short alias: `-p`.
[cli_long_server_port] */
/* [cli_long_listen_address]
\par `--listen-address <local_address>`
Set the listening address. Server and manager addresses may be repeated. Short alias: `-b`.
[cli_long_listen_address] */
/* [cli_long_listen_port]
\par `--listen-port <local_port>`
Set the listening port. Short alias: `-l`.
[cli_long_listen_port] */
/* [cli_long_tproxy]
\par `--tproxy`
Same behavior as `-T`; see that option for details. Short alias: `-T`.
[cli_long_tproxy] */
/* [cli-options]
\snippet{doc} utils.c cli_short_f
\snippet{doc} utils.c cli_short_s
@@ -946,6 +1021,24 @@ Enable UDP relay. Requires Linux TPROXY support and permission to configure tran
\snippet{doc} utils.c cli_short_v
\snippet{doc} utils.c cli_short_6
\snippet{doc} utils.c cli_short_A
\snippet{doc} redir.c cli_long_config
\snippet{doc} redir.c cli_long_cipher
\snippet{doc} redir.c cli_long_timeout
\snippet{doc} redir.c cli_long_user
\snippet{doc} redir.c cli_long_pid_file
\snippet{doc} redir.c cli_long_nofile
\snippet{doc} redir.c cli_long_udp
\snippet{doc} redir.c cli_long_udp_only
\snippet{doc} redir.c cli_long_ipv6_first
\snippet{doc} redir.c cli_long_verbose
\snippet{doc} redir.c cli_long_server
\snippet{doc} redir.c cli_long_server_port
\snippet{doc} redir.c cli_long_listen_address
\snippet{doc} redir.c cli_long_listen_port
\snippet{doc} redir.c cli_long_tproxy
\snippet{doc} utils.c cli_long_version
\snippet{doc} utils.c cli_long_tcp_only
\snippet{doc} utils.c cli_long_ipv4_first
\snippet{doc} utils.c cli_long_fast_open
\snippet{doc} utils.c cli_long_mtu
\snippet{doc} utils.c cli_long_mptcp
@@ -962,6 +1055,24 @@ Enable UDP relay. Requires Linux TPROXY support and permission to configure tran
\snippet{doc} utils.c cli_long_help
[cli-options] */
static struct option long_options[] = {
{ "config", required_argument, NULL, 'c' },
{ "cipher", required_argument, NULL, 'm' },
{ "timeout", required_argument, NULL, 't' },
{ "user", required_argument, NULL, 'a' },
{ "pid-file", required_argument, NULL, 'f' },
{ "nofile", required_argument, NULL, 'n' },
{ "udp", no_argument, NULL, 'u' },
{ "udp-only", no_argument, NULL, 'U' },
{ "ipv6-first", no_argument, NULL, '6' },
{ "verbose", no_argument, NULL, 'v' },
{ "server", required_argument, NULL, 's' },
{ "server-port", required_argument, NULL, 'p' },
{ "listen-address", required_argument, NULL, 'b' },
{ "listen-port", required_argument, NULL, 'l' },
{ "tproxy", no_argument, NULL, 'T' },
{ "version", no_argument, NULL, GETOPT_VAL_VERSION },
{ "tcp-only", no_argument, NULL, GETOPT_VAL_TCP_ONLY },
{ "ipv4-first", no_argument, NULL, GETOPT_VAL_IPV4_FIRST },
{ "fast-open", no_argument, NULL, GETOPT_VAL_FAST_OPEN },
{ "mtu", required_argument, NULL, GETOPT_VAL_MTU },
{ "mptcp", no_argument, NULL, GETOPT_VAL_MPTCP },
@@ -979,19 +1090,32 @@ Enable UDP relay. Requires Linux TPROXY support and permission to configure tran
{ NULL, 0, NULL, 0 }
};
int mode_set = 0;
int ipv6first_set = 0;
opterr = 0;
USE_TTY();
while ((c = getopt_long(argc, argv, "f:s:p:l:k:t:m:c:b:a:n:huUTv6A",
while ((c = getopt_long(argc, argv, ":f:s:p:l:k:t:m:c:b:a:n:huUTv6A",
long_options, NULL)) != -1) {
switch (c) {
case GETOPT_VAL_VERSION:
cli_version();
exit(EXIT_SUCCESS);
case GETOPT_VAL_TCP_ONLY:
mode = TCP_ONLY;
mode_set = 1;
break;
case GETOPT_VAL_IPV4_FIRST:
ipv6first = 0;
ipv6first_set = 1;
break;
case GETOPT_VAL_FAST_OPEN:
fast_open = 1;
break;
case GETOPT_VAL_MTU:
if (ss_parse_int(optarg, 0, INT_MAX, &mtu) == -1) {
FATAL("invalid MTU");
cli_error("invalid MTU", c, NULL);
}
LOGI("set MTU to %d", mtu);
break;
@@ -1018,22 +1142,22 @@ Enable UDP relay. Requires Linux TPROXY support and permission to configure tran
break;
case GETOPT_VAL_TCP_INCOMING_SNDBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_incoming_sndbuf) == -1) {
FATAL("invalid TCP incoming send buffer size");
cli_error("invalid TCP incoming send buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_INCOMING_RCVBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_incoming_rcvbuf) == -1) {
FATAL("invalid TCP incoming receive buffer size");
cli_error("invalid TCP incoming receive buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_OUTGOING_SNDBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_outgoing_sndbuf) == -1) {
FATAL("invalid TCP outgoing send buffer size");
cli_error("invalid TCP outgoing send buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_OUTGOING_RCVBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_outgoing_rcvbuf) == -1) {
FATAL("invalid TCP outgoing receive buffer size");
cli_error("invalid TCP outgoing receive buffer size", c, NULL);
}
break;
case 's':
@@ -1042,9 +1166,19 @@ Enable UDP relay. Requires Linux TPROXY support and permission to configure tran
}
break;
case 'p':
{
uint16_t checked_port;
if (ss_parse_uint16_port(optarg, &checked_port) != 0)
cli_error("port must be an integer from 1 to 65535", c, NULL);
}
remote_port = optarg;
break;
case 'l':
{
uint16_t checked_port;
if (ss_parse_uint16_port(optarg, &checked_port) != 0)
cli_error("port must be an integer from 1 to 65535", c, NULL);
}
local_port = optarg;
break;
case GETOPT_VAL_PASSWORD:
@@ -1056,6 +1190,11 @@ Enable UDP relay. Requires Linux TPROXY support and permission to configure tran
pid_path = optarg;
break;
case 't':
{
int checked_timeout;
if (ss_parse_int(optarg, 1, INT_MAX, &checked_timeout) != 0)
cli_error("timeout must be a positive integer", c, NULL);
}
timeout = optarg;
break;
case 'm':
@@ -1073,14 +1212,16 @@ Enable UDP relay. Requires Linux TPROXY support and permission to configure tran
#ifdef HAVE_SETRLIMIT
case 'n':
if (ss_parse_int(optarg, 0, INT_MAX, &nofile) == -1) {
FATAL("invalid nofile");
cli_error("invalid nofile", c, NULL);
}
break;
#endif
case 'u':
mode_set = 1;
mode = TCP_AND_UDP;
break;
case 'U':
mode_set = 1;
mode = UDP_ONLY;
break;
case 'T':
@@ -1094,22 +1235,26 @@ Enable UDP relay. Requires Linux TPROXY support and permission to configure tran
usage();
exit(EXIT_SUCCESS);
case '6':
ipv6first_set = 1;
ipv6first = 1;
break;
case 'A':
FATAL("One time auth has been deprecated. Try AEAD ciphers instead.");
cli_error("one-time authentication was removed; use an AEAD cipher", 'A', NULL);
break;
case ':':
cli_error("missing required argument for option", optopt, argv[optind - 1]);
break;
case '?':
// The option character is not recognized.
LOGE("Unrecognized option: %s", optarg);
opterr = 1;
cli_error("unrecognized or invalid option", optopt, argv[optind - 1]);
break;
default:
cli_error("option is unsupported on this platform", c, NULL);
break;
}
}
if (opterr) {
usage();
exit(EXIT_FAILURE);
if (optind < argc) {
cli_error("unexpected positional argument", 0, NULL);
}
if (argc == 1) {
@@ -1155,7 +1300,7 @@ Enable UDP relay. Requires Linux TPROXY support and permission to configure tran
if (plugin_opts == NULL) {
plugin_opts = conf->plugin_opts;
}
if (mode == TCP_ONLY) {
if (!mode_set) {
mode = conf->mode;
}
if (tcp_tproxy == 0) {
@@ -1193,7 +1338,7 @@ Enable UDP relay. Requires Linux TPROXY support and permission to configure tran
nofile = conf->nofile;
}
#endif
if (ipv6first == 0) {
if (!ipv6first_set) {
ipv6first = conf->ipv6_first;
}
dscp_num = conf->dscp_num;
+156 -22
View File
@@ -1848,21 +1848,90 @@ Set a server listening hostname or IP address. May be repeated.
Set the server listening port.
[cli_short_p] */
/* [cli_short_l]
\par `-l <local_port>`
Accepted for compatibility but ignored by this program; it does not configure a local listener.
[cli_short_l] */
/* [cli_short_b]
\par `-b <local_address>`
Set the local address for outbound connections to destination servers.
[cli_short_b] */
/* [cli_long_config]
\par `--config <config_file>`
Read JSON configuration. See the CLI conventions for precedence and default-value behavior. Short alias: `-c`.
[cli_long_config] */
/* [cli_long_cipher]
\par `--cipher <encrypt_method>`
Select the encryption cipher. See `-m` for supported cipher names and key requirements. Short alias: `-m`.
[cli_long_cipher] */
/* [cli_long_timeout]
\par `--timeout <timeout>`
Same behavior as `-t`; see that option for details. Short alias: `-t`.
[cli_long_timeout] */
/* [cli_long_user]
\par `--user <user_name>`
Same behavior as `-a`; see that option for details. Short alias: `-a`.
[cli_long_user] */
/* [cli_long_pid_file]
\par `--pid-file <pid_file>`
Same behavior as `-f`; see that option for details. Short alias: `-f`.
[cli_long_pid_file] */
/* [cli_long_nofile]
\par `--nofile <number>`
Same behavior as `-n`; see that option for details. Short alias: `-n`.
[cli_long_nofile] */
/* [cli_long_udp]
\par `--udp`
Enable both TCP and UDP relay, overriding the configured mode. Short alias: `-u`.
[cli_long_udp] */
/* [cli_long_udp_only]
\par `--udp-only`
Enable UDP relay only, overriding the configured mode. Short alias: `-U`.
[cli_long_udp_only] */
/* [cli_long_ipv6_first]
\par `--ipv6-first`
Prefer IPv6 DNS results; overrides configuration. This does not restrict connections to IPv6. Short alias: `-6`.
[cli_long_ipv6_first] */
/* [cli_long_verbose]
\par `--verbose`
Same behavior as `-v`; see that option for details. Short alias: `-v`.
[cli_long_verbose] */
/* [cli_long_listen_address]
\par `--listen-address <server_host>`
Set the listening address. Server and manager addresses may be repeated. Short alias: `-s`.
[cli_long_listen_address] */
/* [cli_long_listen_port]
\par `--listen-port <server_port>`
Set the listening port. Short alias: `-p`.
[cli_long_listen_port] */
/* [cli_long_outbound_address]
\par `--outbound-address <local_address>`
Set the source address for connections to destination servers. Short alias: `-b`.
[cli_long_outbound_address] */
/* [cli_long_interface]
\par `--interface <interface>`
Same behavior as `-i`; see that option for details. Short alias: `-i`.
[cli_long_interface] */
/* [cli_long_nameserver]
\par `--nameserver <addr>`
Same behavior as `-d`; see that option for details. Short alias: `-d`.
[cli_long_nameserver] */
/* [cli-options]
\snippet{doc} utils.c cli_short_f
\snippet{doc} server.c cli_short_s
\snippet{doc} server.c cli_short_p
\snippet{doc} server.c cli_short_l
\snippet{doc} utils.c cli_short_k
\snippet{doc} utils.c cli_short_t
\snippet{doc} utils.c cli_short_m
@@ -1878,6 +1947,24 @@ Set the local address for outbound connections to destination servers.
\snippet{doc} utils.c cli_short_v
\snippet{doc} utils.c cli_short_6
\snippet{doc} utils.c cli_short_A
\snippet{doc} server.c cli_long_config
\snippet{doc} server.c cli_long_cipher
\snippet{doc} server.c cli_long_timeout
\snippet{doc} server.c cli_long_user
\snippet{doc} server.c cli_long_pid_file
\snippet{doc} server.c cli_long_nofile
\snippet{doc} server.c cli_long_udp
\snippet{doc} server.c cli_long_udp_only
\snippet{doc} server.c cli_long_ipv6_first
\snippet{doc} server.c cli_long_verbose
\snippet{doc} server.c cli_long_listen_address
\snippet{doc} server.c cli_long_listen_port
\snippet{doc} server.c cli_long_outbound_address
\snippet{doc} server.c cli_long_interface
\snippet{doc} server.c cli_long_nameserver
\snippet{doc} utils.c cli_long_version
\snippet{doc} utils.c cli_long_tcp_only
\snippet{doc} utils.c cli_long_ipv4_first
\snippet{doc} utils.c cli_long_fast_open
\snippet{doc} utils.c cli_long_reuse_port
\snippet{doc} utils.c cli_long_tcp_incoming_sndbuf
@@ -1897,6 +1984,24 @@ Set the local address for outbound connections to destination servers.
\snippet{doc} utils.c cli_long_nftables_sets
[cli-options] */
static struct option long_options[] = {
{ "config", required_argument, NULL, 'c' },
{ "cipher", required_argument, NULL, 'm' },
{ "timeout", required_argument, NULL, 't' },
{ "user", required_argument, NULL, 'a' },
{ "pid-file", required_argument, NULL, 'f' },
{ "nofile", required_argument, NULL, 'n' },
{ "udp", no_argument, NULL, 'u' },
{ "udp-only", no_argument, NULL, 'U' },
{ "ipv6-first", no_argument, NULL, '6' },
{ "verbose", no_argument, NULL, 'v' },
{ "listen-address", required_argument, NULL, 's' },
{ "listen-port", required_argument, NULL, 'p' },
{ "outbound-address", required_argument, NULL, 'b' },
{ "interface", required_argument, NULL, 'i' },
{ "nameserver", required_argument, NULL, 'd' },
{ "version", no_argument, NULL, GETOPT_VAL_VERSION },
{ "tcp-only", no_argument, NULL, GETOPT_VAL_TCP_ONLY },
{ "ipv4-first", no_argument, NULL, GETOPT_VAL_IPV4_FIRST },
{ "fast-open", no_argument, NULL, GETOPT_VAL_FAST_OPEN },
{ "reuse-port", no_argument, NULL, GETOPT_VAL_REUSE_PORT },
{ "tcp-incoming-sndbuf", required_argument, NULL, GETOPT_VAL_TCP_INCOMING_SNDBUF },
@@ -1922,13 +2027,26 @@ Set the local address for outbound connections to destination servers.
{ NULL, 0, NULL, 0 }
};
int mode_set = 0;
int ipv6first_set = 0;
opterr = 0;
USE_TTY();
while ((c = getopt_long(argc, argv, "f:s:p:l:k:t:m:b:c:i:d:a:n:huUv6A",
while ((c = getopt_long(argc, argv, ":f:s:p:k:t:m:b:c:i:d:a:n:huUv6A",
long_options, NULL)) != -1) {
switch (c) {
case GETOPT_VAL_VERSION:
cli_version();
exit(EXIT_SUCCESS);
case GETOPT_VAL_TCP_ONLY:
mode = TCP_ONLY;
mode_set = 1;
break;
case GETOPT_VAL_IPV4_FIRST:
ipv6first = 0;
ipv6first_set = 1;
break;
case GETOPT_VAL_FAST_OPEN:
fast_open = 1;
break;
@@ -1945,7 +2063,7 @@ Set the local address for outbound connections to destination servers.
break;
case GETOPT_VAL_MTU:
if (ss_parse_int(optarg, 0, INT_MAX, &mtu) == -1) {
FATAL("invalid MTU");
cli_error("invalid MTU", c, NULL);
}
LOGI("set MTU to %d", mtu);
break;
@@ -1968,22 +2086,22 @@ Set the local address for outbound connections to destination servers.
break;
case GETOPT_VAL_TCP_INCOMING_SNDBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_incoming_sndbuf) == -1) {
FATAL("invalid TCP incoming send buffer size");
cli_error("invalid TCP incoming send buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_INCOMING_RCVBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_incoming_rcvbuf) == -1) {
FATAL("invalid TCP incoming receive buffer size");
cli_error("invalid TCP incoming receive buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_OUTGOING_SNDBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_outgoing_sndbuf) == -1) {
FATAL("invalid TCP outgoing send buffer size");
cli_error("invalid TCP outgoing send buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_OUTGOING_RCVBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_outgoing_rcvbuf) == -1) {
FATAL("invalid TCP outgoing receive buffer size");
cli_error("invalid TCP outgoing receive buffer size", c, NULL);
}
break;
#ifdef USE_NFTABLES
@@ -2000,6 +2118,11 @@ Set the local address for outbound connections to destination servers.
is_bind_local_addr += parse_local_addr(&local_addr_v4, &local_addr_v6, optarg);
break;
case 'p':
{
uint16_t checked_port;
if (ss_parse_uint16_port(optarg, &checked_port) != 0)
cli_error("port must be an integer from 1 to 65535", c, NULL);
}
server_port = optarg;
break;
case GETOPT_VAL_PASSWORD:
@@ -2011,6 +2134,11 @@ Set the local address for outbound connections to destination servers.
pid_path = optarg;
break;
case 't':
{
int checked_timeout;
if (ss_parse_int(optarg, 1, INT_MAX, &checked_timeout) != 0)
cli_error("timeout must be a positive integer", c, NULL);
}
timeout = optarg;
break;
case 'm':
@@ -2031,14 +2159,16 @@ Set the local address for outbound connections to destination servers.
#ifdef HAVE_SETRLIMIT
case 'n':
if (ss_parse_int(optarg, 0, INT_MAX, &nofile) == -1) {
FATAL("invalid nofile");
cli_error("invalid nofile", c, NULL);
}
break;
#endif
case 'u':
mode_set = 1;
mode = TCP_AND_UDP;
break;
case 'U':
mode_set = 1;
mode = UDP_ONLY;
break;
case 'v':
@@ -2049,22 +2179,26 @@ Set the local address for outbound connections to destination servers.
usage();
exit(EXIT_SUCCESS);
case '6':
ipv6first_set = 1;
ipv6first = 1;
break;
case 'A':
FATAL("One time auth has been deprecated. Try AEAD ciphers instead.");
cli_error("one-time authentication was removed; use an AEAD cipher", 'A', NULL);
break;
case ':':
cli_error("missing required argument for option", optopt, argv[optind - 1]);
break;
case '?':
// The option character is not recognized.
LOGE("Unrecognized option: %s", optarg);
opterr = 1;
cli_error("unrecognized or invalid option", optopt, argv[optind - 1]);
break;
default:
cli_error("option is unsupported on this platform", c, NULL);
break;
}
}
if (opterr) {
usage();
exit(EXIT_FAILURE);
if (optind < argc) {
cli_error("unexpected positional argument", 0, NULL);
}
if (argc == 1) {
@@ -2104,7 +2238,7 @@ Set the local address for outbound connections to destination servers.
if (plugin_opts == NULL) {
plugin_opts = conf->plugin_opts;
}
if (mode == TCP_ONLY) {
if (!mode_set) {
mode = conf->mode;
}
if (mtu == 0) {
@@ -2149,7 +2283,7 @@ Set the local address for outbound connections to destination servers.
if (nameservers == NULL) {
nameservers = conf->nameserver;
}
if (ipv6first == 0) {
if (!ipv6first_set) {
ipv6first = conf->ipv6_first;
}
if (acl == 0 && conf->acl != NULL) {
+180 -16
View File
@@ -957,6 +957,91 @@ main(int argc, char **argv)
memset(remote_addr, 0, sizeof(ss_addr_t) * MAX_REMOTE_NUM);
/* [cli_long_config]
\par `--config <config_file>`
Read JSON configuration. See the CLI conventions for precedence and default-value behavior. Short alias: `-c`.
[cli_long_config] */
/* [cli_long_cipher]
\par `--cipher <encrypt_method>`
Select the encryption cipher. See `-m` for supported cipher names and key requirements. Short alias: `-m`.
[cli_long_cipher] */
/* [cli_long_timeout]
\par `--timeout <timeout>`
Same behavior as `-t`; see that option for details. Short alias: `-t`.
[cli_long_timeout] */
/* [cli_long_user]
\par `--user <user_name>`
Same behavior as `-a`; see that option for details. Short alias: `-a`.
[cli_long_user] */
/* [cli_long_pid_file]
\par `--pid-file <pid_file>`
Same behavior as `-f`; see that option for details. Short alias: `-f`.
[cli_long_pid_file] */
/* [cli_long_nofile]
\par `--nofile <number>`
Same behavior as `-n`; see that option for details. Short alias: `-n`.
[cli_long_nofile] */
/* [cli_long_udp]
\par `--udp`
Enable both TCP and UDP relay, overriding the configured mode. Short alias: `-u`.
[cli_long_udp] */
/* [cli_long_udp_only]
\par `--udp-only`
Enable UDP relay only, overriding the configured mode. Short alias: `-U`.
[cli_long_udp_only] */
/* [cli_long_ipv6_first]
\par `--ipv6-first`
Prefer IPv6 DNS results; overrides configuration. This does not restrict connections to IPv6. Short alias: `-6`.
[cli_long_ipv6_first] */
/* [cli_long_verbose]
\par `--verbose`
Same behavior as `-v`; see that option for details. Short alias: `-v`.
[cli_long_verbose] */
/* [cli_long_server]
\par `--server <server_host>`
Set a remote Shadowsocks server hostname or IP address; may be repeated. Short alias: `-s`.
[cli_long_server] */
/* [cli_long_server_port]
\par `--server-port <server_port>`
Set the remote Shadowsocks server port. Short alias: `-p`.
[cli_long_server_port] */
/* [cli_long_listen_address]
\par `--listen-address <local_address>`
Set the listening address. Server and manager addresses may be repeated. Short alias: `-b`.
[cli_long_listen_address] */
/* [cli_long_listen_port]
\par `--listen-port <local_port>`
Set the listening port. Short alias: `-l`.
[cli_long_listen_port] */
/* [cli_long_interface]
\par `--interface <interface>`
Same behavior as `-i`; see that option for details. Short alias: `-i`.
[cli_long_interface] */
/* [cli_long_destination]
\par `--destination <addr:port>`
Set the forwarding destination as HOST:PORT; use [IPv6]:PORT for an IPv6 literal. Short alias: `-L`.
[cli_long_destination] */
/* [cli_long_vpn]
\par `--vpn`
Same behavior as `-V`; see that option for details. Short alias: `-V`.
[cli_long_vpn] */
/* [cli-options]
\snippet{doc} utils.c cli_short_f
\snippet{doc} utils.c cli_short_s
@@ -978,6 +1063,26 @@ main(int argc, char **argv)
\snippet{doc} utils.c cli_short_V
\snippet{doc} utils.c cli_short_6
\snippet{doc} utils.c cli_short_A
\snippet{doc} tunnel.c cli_long_config
\snippet{doc} tunnel.c cli_long_cipher
\snippet{doc} tunnel.c cli_long_timeout
\snippet{doc} tunnel.c cli_long_user
\snippet{doc} tunnel.c cli_long_pid_file
\snippet{doc} tunnel.c cli_long_nofile
\snippet{doc} tunnel.c cli_long_udp
\snippet{doc} tunnel.c cli_long_udp_only
\snippet{doc} tunnel.c cli_long_ipv6_first
\snippet{doc} tunnel.c cli_long_verbose
\snippet{doc} tunnel.c cli_long_server
\snippet{doc} tunnel.c cli_long_server_port
\snippet{doc} tunnel.c cli_long_listen_address
\snippet{doc} tunnel.c cli_long_listen_port
\snippet{doc} tunnel.c cli_long_interface
\snippet{doc} tunnel.c cli_long_destination
\snippet{doc} utils.c cli_long_version
\snippet{doc} utils.c cli_long_tcp_only
\snippet{doc} utils.c cli_long_ipv4_first
\snippet{doc} tunnel.c cli_long_vpn
\snippet{doc} utils.c cli_long_fast_open
\snippet{doc} utils.c cli_long_mtu
\snippet{doc} utils.c cli_long_no_delay
@@ -994,6 +1099,28 @@ main(int argc, char **argv)
\snippet{doc} utils.c cli_long_help
[cli-options] */
static struct option long_options[] = {
{ "config", required_argument, NULL, 'c' },
{ "cipher", required_argument, NULL, 'm' },
{ "timeout", required_argument, NULL, 't' },
{ "user", required_argument, NULL, 'a' },
{ "pid-file", required_argument, NULL, 'f' },
{ "nofile", required_argument, NULL, 'n' },
{ "udp", no_argument, NULL, 'u' },
{ "udp-only", no_argument, NULL, 'U' },
{ "ipv6-first", no_argument, NULL, '6' },
{ "verbose", no_argument, NULL, 'v' },
{ "server", required_argument, NULL, 's' },
{ "server-port", required_argument, NULL, 'p' },
{ "listen-address", required_argument, NULL, 'b' },
{ "listen-port", required_argument, NULL, 'l' },
{ "interface", required_argument, NULL, 'i' },
{ "destination", required_argument, NULL, 'L' },
{ "version", no_argument, NULL, GETOPT_VAL_VERSION },
{ "tcp-only", no_argument, NULL, GETOPT_VAL_TCP_ONLY },
{ "ipv4-first", no_argument, NULL, GETOPT_VAL_IPV4_FIRST },
#ifdef __ANDROID__
{ "vpn", no_argument, NULL, 'V' },
#endif
{ "fast-open", no_argument, NULL, GETOPT_VAL_FAST_OPEN },
{ "mtu", required_argument, NULL, GETOPT_VAL_MTU },
{ "no-delay", no_argument, NULL, GETOPT_VAL_NODELAY },
@@ -1011,24 +1138,37 @@ main(int argc, char **argv)
{ NULL, 0, NULL, 0 }
};
int mode_set = 0;
int ipv6first_set = 0;
opterr = 0;
USE_TTY();
#ifdef __ANDROID__
while ((c = getopt_long(argc, argv, "f:s:p:l:k:t:m:i:c:b:L:a:n:huUvV6A",
while ((c = getopt_long(argc, argv, ":f:s:p:l:k:t:m:i:c:b:L:a:n:huUvV6A",
long_options, NULL)) != -1) {
#else
while ((c = getopt_long(argc, argv, "f:s:p:l:k:t:m:i:c:b:L:a:n:huUv6A",
while ((c = getopt_long(argc, argv, ":f:s:p:l:k:t:m:i:c:b:L:a:n:huUv6A",
long_options, NULL)) != -1) {
#endif
switch (c) {
case GETOPT_VAL_VERSION:
cli_version();
exit(EXIT_SUCCESS);
case GETOPT_VAL_TCP_ONLY:
mode = TCP_ONLY;
mode_set = 1;
break;
case GETOPT_VAL_IPV4_FIRST:
ipv6first = 0;
ipv6first_set = 1;
break;
case GETOPT_VAL_FAST_OPEN:
fast_open = 1;
break;
case GETOPT_VAL_MTU:
if (ss_parse_int(optarg, 0, INT_MAX, &mtu) == -1) {
FATAL("invalid MTU");
cli_error("invalid MTU", c, NULL);
}
LOGI("set MTU to %d", mtu);
break;
@@ -1055,22 +1195,22 @@ main(int argc, char **argv)
break;
case GETOPT_VAL_TCP_INCOMING_SNDBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_incoming_sndbuf) == -1) {
FATAL("invalid TCP incoming send buffer size");
cli_error("invalid TCP incoming send buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_INCOMING_RCVBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_incoming_rcvbuf) == -1) {
FATAL("invalid TCP incoming receive buffer size");
cli_error("invalid TCP incoming receive buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_OUTGOING_SNDBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_outgoing_sndbuf) == -1) {
FATAL("invalid TCP outgoing send buffer size");
cli_error("invalid TCP outgoing send buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_OUTGOING_RCVBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_outgoing_rcvbuf) == -1) {
FATAL("invalid TCP outgoing receive buffer size");
cli_error("invalid TCP outgoing receive buffer size", c, NULL);
}
break;
case 's':
@@ -1079,9 +1219,19 @@ main(int argc, char **argv)
}
break;
case 'p':
{
uint16_t checked_port;
if (ss_parse_uint16_port(optarg, &checked_port) != 0)
cli_error("port must be an integer from 1 to 65535", c, NULL);
}
remote_port = optarg;
break;
case 'l':
{
uint16_t checked_port;
if (ss_parse_uint16_port(optarg, &checked_port) != 0)
cli_error("port must be an integer from 1 to 65535", c, NULL);
}
local_port = optarg;
break;
case GETOPT_VAL_PASSWORD:
@@ -1093,6 +1243,11 @@ main(int argc, char **argv)
pid_path = optarg;
break;
case 't':
{
int checked_timeout;
if (ss_parse_int(optarg, 1, INT_MAX, &checked_timeout) != 0)
cli_error("timeout must be a positive integer", c, NULL);
}
timeout = optarg;
break;
case 'm':
@@ -1108,9 +1263,11 @@ main(int argc, char **argv)
local_addr = optarg;
break;
case 'u':
mode_set = 1;
mode = TCP_AND_UDP;
break;
case 'U':
mode_set = 1;
mode = UDP_ONLY;
break;
case 'L':
@@ -1122,7 +1279,7 @@ main(int argc, char **argv)
#ifdef HAVE_SETRLIMIT
case 'n':
if (ss_parse_int(optarg, 0, INT_MAX, &nofile) == -1) {
FATAL("invalid nofile");
cli_error("invalid nofile", c, NULL);
}
break;
#endif
@@ -1134,6 +1291,7 @@ main(int argc, char **argv)
usage();
exit(EXIT_SUCCESS);
case '6':
ipv6first_set = 1;
ipv6first = 1;
break;
#ifdef __ANDROID__
@@ -1142,19 +1300,22 @@ main(int argc, char **argv)
break;
#endif
case 'A':
FATAL("One time auth has been deprecated. Try AEAD ciphers instead.");
cli_error("one-time authentication was removed; use an AEAD cipher", 'A', NULL);
break;
case ':':
cli_error("missing required argument for option", optopt, argv[optind - 1]);
break;
case '?':
// The option character is not recognized.
LOGE("Unrecognized option: %s", optarg);
opterr = 1;
cli_error("unrecognized or invalid option", optopt, argv[optind - 1]);
break;
default:
cli_error("option is unsupported on this platform", c, NULL);
break;
}
}
if (opterr) {
usage();
exit(EXIT_FAILURE);
if (optind < argc) {
cli_error("unexpected positional argument", 0, NULL);
}
if (argc == 1) {
@@ -1203,7 +1364,10 @@ main(int argc, char **argv)
if (tunnel_addr_str == NULL) {
tunnel_addr_str = conf->tunnel_address;
}
if (mode == TCP_ONLY) {
if (!ipv6first_set) {
ipv6first = conf->ipv6_first;
}
if (!mode_set) {
mode = conf->mode;
}
if (mtu == 0) {
+148 -147
View File
@@ -369,7 +369,7 @@ some require backend support):
\snippet stream.c cli-stream-ciphers
The `2022-blake3-*` ciphers implement Shadowsocks 2022 (SIP022). They require
a base64-encoded pre-shared key supplied with *-k*: 16 bytes for
a base64-encoded pre-shared key supplied with `--key` or `--password` (*-k*): 16 bytes for
2022-blake3-aes-128-gcm and 32 bytes for the other 2022 ciphers.
Generate a 32-byte key with `openssl rand -base64 32`.
Passwords are not stretched into keys for these ciphers.
@@ -409,8 +409,7 @@ Send outbound traffic through the specified network interface where supported by
/* [cli_short_b]
\par `-b <local_address>`
Specify the local address to use while this client is making outbound
connections to the server.
Set the local address to bind for the client listener.
[cli_short_b] */
/* [cli_short_u]
@@ -560,167 +559,169 @@ Set the executable path of ss-server used by ss-manager.
Linux builds with USE_NFTABLES only: add malicious IP addresses to nftables sets. Format: `[<table1>:]<set1>[,[<table2>:]<set2>...]`.
[cli_long_nftables_sets] */
void
usage()
/* [cli_long_version]
\par `--version`
Print the program name and version to standard output and exit successfully.
[cli_long_version] */
/* [cli_long_tcp_only]
\par `--tcp-only`
Enable TCP relay only, overriding the configuration file's mode.
The last of `--tcp-only`, `--udp`, and `--udp-only` wins.
[cli_long_tcp_only] */
/* [cli_long_ipv4_first]
\par `--ipv4-first`
Prefer IPv4 DNS results, overriding `ipv6_first` in the configuration file.
This is an address preference, not a restriction to IPv4. The last of
`--ipv4-first` and `--ipv6-first` wins.
[cli_long_ipv4_first] */
static const char *
cli_program(void)
{
printf("\n");
printf("shadowsocks-c %s\n\n", VERSION);
printf(
" maintained by Max Lv <max.c.lv@gmail.com> and Linus Yang <laokongzi@gmail.com>\n\n");
printf(" usage:\n\n");
#ifdef MODULE_LOCAL
printf(" ss-local\n");
#elif MODULE_REMOTE
printf(" ss-server\n");
#elif MODULE_TUNNEL
printf(" ss-tunnel\n");
#elif MODULE_REDIR
printf(" ss-redir\n");
#elif MODULE_MANAGER
printf(" ss-manager\n");
#endif
printf("\n");
printf(
" -s <server_host> Host name or IP address of your remote server.\n");
printf(
" -p <server_port> Port number of your remote server.\n");
printf(
" -l <local_port> Port number of your local server.\n");
printf(
" -k <password> Password of your remote server.\n");
printf(
" -m <encrypt_method> Encrypt method: rc4-md5, \n");
printf(
" aes-128-gcm, aes-192-gcm, aes-256-gcm,\n");
printf(
" aes-128-cfb, aes-192-cfb, aes-256-cfb,\n");
printf(
" aes-128-ctr, aes-192-ctr, aes-256-ctr,\n");
printf(
" camellia-128-cfb, camellia-192-cfb,\n");
printf(
" camellia-256-cfb, bf-cfb,\n");
printf(
" chacha20-ietf-poly1305,\n");
#ifdef FS_HAVE_XCHACHA20IETF
printf(
" xchacha20-ietf-poly1305,\n");
#endif
printf(
" salsa20, chacha20 and chacha20-ietf.\n");
printf(
" The default cipher is chacha20-ietf-poly1305.\n");
printf("\n");
printf(
" [-a <user>] Run as another user.\n");
printf(
" [-f <pid_file>] The file path to store pid.\n");
printf(
" [-t <timeout>] Socket timeout in seconds.\n");
printf(
" [-c <config_file>] The path to config file.\n");
#ifdef HAVE_SETRLIMIT
printf(
" [-n <number>] Max number of open files.\n");
#endif
#ifndef MODULE_REDIR
printf(
" [-i <interface>] Network interface to bind.\n");
#endif
printf(
" [-b <local_address>] Local address to bind.\n");
printf("\n");
printf(
" [-u] Enable UDP relay.\n");
#ifdef MODULE_REDIR
printf(
" TPROXY is required in redir mode.\n");
#endif
printf(
" [-U] Enable UDP relay and disable TCP relay.\n");
#ifdef MODULE_REDIR
printf(
" [-T] Use tproxy instead of redirect (for tcp).\n");
return "ss-local";
#elif defined(MODULE_REMOTE)
return "ss-server";
#elif defined(MODULE_TUNNEL)
return "ss-tunnel";
#elif defined(MODULE_REDIR)
return "ss-redir";
#elif defined(MODULE_MANAGER)
return "ss-manager";
#else
return "shadowsocks-c";
#endif
}
void
cli_version(void)
{
printf("%s (shadowsocks-c) %s\n", cli_program(), VERSION);
}
void
cli_error(const char *message, int option, const char *token)
{
fprintf(stderr, "%s: %s", cli_program(), message);
/* Report option names, never attached passwords or positional values. */
if (token != NULL && token[0] == '-' && token[1] == '-') {
size_t length = 2;
while (isalnum((unsigned char)token[length]) || token[length] == '-')
length++;
if (length > 2 && length < 80)
fprintf(stderr, " '%.*s'", (int)length, token);
} else if (option > 0 && option < 128 && isalnum((unsigned char)option)) {
fprintf(stderr, " '-%c'", option);
}
fprintf(stderr, ". Try '%s --help'.\n", cli_program());
exit(2);
}
static void
cli_help_option(const char *flags, const char *description)
{
printf(" %-36s %s\n", flags, description);
}
void
usage(void)
{
cli_version();
printf("Usage: %s [options]\n\n", cli_program());
puts("Connection:");
#if defined(MODULE_REMOTE) || defined(MODULE_MANAGER)
cli_help_option("-s, --listen-address HOST", "Server listening address; may be repeated.");
#ifdef MODULE_REMOTE
printf(
" [-6] Resovle hostname to IPv6 address first.\n");
cli_help_option("-p, --listen-port PORT", "Server listening port.");
cli_help_option("-b, --outbound-address ADDRESS", "Source address for outbound connections.");
#endif
#else
cli_help_option("-s, --server HOST", "Remote server hostname or IP; may be repeated.");
cli_help_option("-p, --server-port PORT", "Remote server port.");
cli_help_option("-b, --listen-address ADDRESS", "Local address to bind.");
cli_help_option("-l, --listen-port PORT", "Local listening port.");
#endif
#ifdef MODULE_LOCAL
cli_help_option("--server-url URL", "Import an ss:// URL; later options override it.");
#endif
printf("\n");
#ifdef MODULE_TUNNEL
printf(
" [-L <addr>:<port>] Destination server address and port\n");
printf(
" for local port forwarding.\n");
cli_help_option("-L, --destination HOST:PORT", "Destination for local port forwarding.");
#endif
#ifdef MODULE_REMOTE
printf(
" [-d <addr>] Name servers for internal DNS resolver.\n");
puts("\nConfiguration and credentials:");
cli_help_option("-c, --config FILE", "Read JSON configuration.");
cli_help_option("-m, --cipher NAME", "Cipher (default: chacha20-ietf-poly1305).");
cli_help_option("-k, --password SECRET", "Password, or a base64 pre-shared key for AEAD-2022.");
#ifndef MODULE_MANAGER
cli_help_option("--key BASE64", "Use an explicit base64 key instead of a password.");
#endif
printf(
" [--reuse-port] Enable port reuse.\n");
#if defined(MODULE_REMOTE) || defined(MODULE_LOCAL) || defined(MODULE_REDIR)
printf(
" [--fast-open] Enable TCP fast open.\n");
printf(
" with Linux kernel > 3.7.0.\n");
#endif
printf(
" [--tcp-incoming-sndbuf] Size of the incoming connection TCP send buffer.\n");
printf(
" [--tcp-incoming-rcvbuf] Size of the incoming connection TCP receive buffer.\n");
printf(
" [--tcp-outgoing-sndbuf] Size of the outgoing connection TCP send buffer.\n");
printf(
" [--tcp-outgoing-rcvbuf] Size of the outgoing connection TCP receive buffer.\n");
#if defined(MODULE_REMOTE) || defined(MODULE_LOCAL)
printf(
" [--acl <acl_file>] Path to ACL (Access Control List).\n");
puts(" AEAD-2022: 2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm,");
puts(" 2022-blake3-chacha20-poly1305.");
puts("\nTransport and networking:");
cli_help_option("--tcp-only", "TCP only (default); overrides the configured mode.");
cli_help_option("-u, --udp", "Enable both TCP and UDP relay.");
cli_help_option("-U, --udp-only", "Enable UDP relay only.");
cli_help_option("--ipv4-first", "Prefer IPv4 DNS results; overrides configuration.");
cli_help_option("-6, --ipv6-first", "Prefer IPv6 DNS results.");
cli_help_option("-t, --timeout SECONDS", "Socket timeout (default: 60).");
#ifndef MODULE_REDIR
cli_help_option("-i, --interface NAME", "Outbound network interface, where supported.");
#endif
#if defined(MODULE_REMOTE) || defined(MODULE_MANAGER)
printf(
" [--manager-address <addr>] UNIX domain socket address.\n");
cli_help_option("-d, --nameserver ADDRESS", "Name servers for the internal DNS resolver.");
#endif
#ifdef MODULE_MANAGER
printf(
" [--executable <path>] Path to the executable of ss-server.\n");
printf(
" [-D <path>] Path to the working directory of ss-manager.\n");
#endif
printf(
" [--mtu <MTU>] MTU of your network interface.\n");
#ifdef __linux__
printf(
" [--mptcp] Enable Multipath TCP on MPTCP Kernel.\n");
#ifdef USE_NFTABLES
printf(
" [--nftables-sets <sets>] Add malicious IP into nftables sets.\n");
printf(
" sets spec: [<table1>:]<set1>[,[<table2>:]<set2>...]\n");
#ifdef MODULE_REDIR
cli_help_option("-T, --tproxy", "Use TPROXY for TCP; UDP always requires TPROXY.");
#endif
cli_help_option("--mtu BYTES", "Network MTU (0 selects the default).");
cli_help_option("--fast-open", "Enable TCP Fast Open where supported.");
cli_help_option("--reuse-port", "Enable port reuse where supported.");
cli_help_option("--no-delay", "Enable TCP_NODELAY.");
#if !defined(MODULE_MANAGER) && (!defined(MODULE_REMOTE) || defined(__linux__))
cli_help_option("--mptcp", "Enable Multipath TCP where supported.");
#endif
#ifndef MODULE_MANAGER
printf(
" [--no-delay] Enable TCP_NODELAY.\n");
printf(
" [--key <key_in_base64>] Key of your remote server.\n");
cli_help_option("--tcp-incoming-sndbuf BYTES", "Incoming TCP send buffer (0: system default).");
cli_help_option("--tcp-incoming-rcvbuf BYTES", "Incoming TCP receive buffer (0: system default).");
cli_help_option("--tcp-outgoing-sndbuf BYTES", "Outgoing TCP send buffer (0: system default).");
cli_help_option("--tcp-outgoing-rcvbuf BYTES", "Outgoing TCP receive buffer (0: system default).");
#endif
puts("\nAccess control and plugins:");
#if defined(MODULE_LOCAL) || defined(MODULE_REMOTE) || defined(MODULE_MANAGER)
cli_help_option("--acl FILE", "Access control list.");
#endif
#if defined(MODULE_REMOTE) && defined(__linux__) && defined(USE_NFTABLES)
cli_help_option("--nftables-sets SETS", "Record malicious IPs in [table:]set[,set...] entries.");
#endif
cli_help_option("--plugin NAME", "SIP003 plugin (requires plugin support in this build).");
cli_help_option("--plugin-opts OPTIONS", "Options passed to the SIP003 plugin.");
puts("\nProcess and diagnostics:");
cli_help_option("-a, --user USER", "Run as the specified user.");
cli_help_option("-f, --pid-file FILE", "Daemonize and write the process ID to FILE.");
#ifdef HAVE_SETRLIMIT
cli_help_option("-n, --nofile COUNT", "Maximum number of open files.");
#endif
#if defined(MODULE_REMOTE) || defined(MODULE_MANAGER)
cli_help_option("--manager-address ADDRESS", "Manager UNIX socket path or IP address and port.");
#endif
#ifdef MODULE_MANAGER
cli_help_option("--executable PATH", "ss-server executable used for managed servers.");
cli_help_option("-D, --workdir DIRECTORY", "Working directory for managed servers.");
#endif
#if defined(__ANDROID__) && (defined(MODULE_LOCAL) || defined(MODULE_TUNNEL))
cli_help_option("-V, --vpn", "Enable Android VPN socket protection.");
#ifdef MODULE_LOCAL
printf(
" [--server-url <ss_url>] Server as an ss:// URL, carrying the\n");
printf(
" address, cipher, password and plugin.\n");
cli_help_option("-S, --stat-path PATH", "Android traffic-statistics socket.");
#endif
printf(
" [--plugin <name>] Enable SIP003 plugin. (Experimental)\n");
printf(
" [--plugin-opts <options>] Set SIP003 plugin options. (Experimental)\n");
printf("\n");
printf(
" [-v] Verbose mode.\n");
printf(
" [-h, --help] Print this message.\n");
printf("\n");
#endif
cli_help_option("-v, --verbose", "Enable verbose logging.");
cli_help_option("-h, --help", "Show this help and exit.");
cli_help_option("--version", "Show the version and exit.");
puts("\nOptions apply in command-line order; later mode and address-family flags win.");
puts("No positional arguments are accepted. Use --option=value for values starting with '-'.");
puts("The obsolete -A option is rejected; use an AEAD cipher instead.");
puts("Manual: https://shadowsocks.github.io/shadowsocks-c/");
}
void
+2
View File
@@ -219,6 +219,8 @@ int ss_parse_uint16_port(const char *s, uint16_t *out);
int run_as(const char *user);
void FATAL(const char *msg) __attribute__((noreturn));
void usage(void);
void cli_version(void);
void cli_error(const char *message, int option, const char *token) __attribute__((noreturn));
void daemonize(const char *path);
char *ss_strndup(const char *s, size_t n);
#ifdef HAVE_SETRLIMIT
+10
View File
@@ -214,3 +214,13 @@ ss_add_test(test_resolv_async
"test_resolv_async.c;${PROJECT_SOURCE_DIR}/src/resolv.c;${PROJECT_SOURCE_DIR}/src/utils.c"
"ss_ev;ss_cares;ss_sodium")
set_tests_properties(test_resolv_async PROPERTIES TIMEOUT 30)
if(SS_BUILD_EXECUTABLES AND NOT CMAKE_CROSSCOMPILING)
find_package(Python3 COMPONENTS Interpreter QUIET)
if(Python3_Interpreter_FOUND)
add_test(NAME test_cli
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_cli.py
--bin-dir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
set_tests_properties(test_cli PROPERTIES LABELS "integration" TIMEOUT 60)
endif()
endif()
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""Exercise CLI parsing, diagnostics, and configuration overrides on real binaries."""
import argparse
import json
import os
from pathlib import Path
import socket
import subprocess
import tempfile
import time
import unittest
BIN_DIR = Path(os.environ.get('SS_BIN_DIR', 'build/bin')).resolve()
class CliTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
suffix = '.exe' if os.name == 'nt' else ''
cls.binaries = {name: BIN_DIR / ('ss-' + name + suffix)
for name in ('local', 'server', 'tunnel', 'redir', 'manager')
if (BIN_DIR / ('ss-' + name + suffix)).is_file()}
for name in ('local', 'server', 'tunnel'):
if name not in cls.binaries:
raise RuntimeError(f'Missing required CLI binary: ss-{name}')
def run_cli(self, name, *arguments):
return subprocess.run([str(self.binaries[name]), *arguments],
capture_output=True, text=True, timeout=10)
def test_help_and_version(self):
for name in self.binaries:
with self.subTest(name=name):
help_result = self.run_cli(name, '--help')
self.assertEqual(help_result.returncode, 0)
self.assertEqual(help_result.stderr, '')
for flag in ('--config', '--cipher', '--tcp-only', '--udp-only',
'--ipv4-first', '--ipv6-first', '--version'):
self.assertIn(flag, help_result.stdout)
version = self.run_cli(name, '--version')
self.assertEqual(version.returncode, 0)
self.assertRegex(version.stdout, rf'^ss-{name} \(shadowsocks-c\) \d+\.\d+')
self.assertEqual(len(version.stdout.splitlines()), 1)
self.assertEqual(version.stderr, '')
if name == 'manager':
self.assertNotIn('--server-port', help_result.stdout)
self.assertNotIn('--listen-port', help_result.stdout)
self.assertNotIn('--tcp-incoming-sndbuf', help_result.stdout)
self.assertNotIn('--mptcp', help_result.stdout)
elif name == 'server':
self.assertIn('--outbound-address', help_result.stdout)
self.assertNotIn('--server-port', help_result.stdout)
def test_long_aliases_accept_their_arguments(self):
for name in self.binaries:
args = ['--config', 'not-loaded-before-help.json', '--cipher', 'aes-128-gcm',
'--password', 'test-password', '--timeout', '60', '--user', 'nobody',
'--pid-file', 'not-created.pid', '--verbose', '--udp', '--udp-only',
'--tcp-only', '--ipv6-first', '--ipv4-first', '--listen-address', '127.0.0.1']
if name not in ('server', 'manager'):
args += ['--server', '127.0.0.1', '--server-port', '8388']
if name != 'manager':
args += ['--listen-port', '1080']
if name == 'server':
args += ['--outbound-address', '127.0.0.1']
if name != 'redir':
args += ['--interface', 'lo']
if name in ('server', 'manager'):
args += ['--nameserver', '127.0.0.1']
if name == 'tunnel':
args += ['--destination', '127.0.0.1:80']
if name == 'redir':
args += ['--tproxy']
result = self.run_cli(name, *args, '--help')
self.assertEqual(result.returncode, 0, (name, result.stderr))
self.assertIn('Usage:', result.stdout)
def test_usage_errors_are_stderr_exit_two_without_secrets(self):
secret = 'private-argument-value'
for name in self.binaries:
for args in (['--unknown=' + secret], ['-Z' + secret], [secret],
['--', secret], ['--password'], ['-k'], ['-A']):
with self.subTest(name=name, arguments=args[:1]):
result = self.run_cli(name, *args)
self.assertEqual(result.returncode, 2)
self.assertEqual(result.stdout, '')
self.assertIn('--help', result.stderr)
self.assertNotIn(secret, result.stderr)
result = self.run_cli(name, '--password')
self.assertIn('missing required argument', result.stderr)
self.assertIn('--password', result.stderr)
for name in ('server', 'manager'):
if name in self.binaries:
self.assertEqual(self.run_cli(name, '-l', '1080').returncode, 2)
def test_transport_and_address_family_override_configuration(self):
cases = [(['--tcp-only', '--ipv4-first'], False, False),
(['--udp-only', '--tcp-only', '--ipv6-first', '--ipv4-first'], False, False),
(['--tcp-only', '--udp', '--ipv4-first', '--ipv6-first'], True, True)]
for args, udp, ipv6 in cases:
with self.subTest(args=args), tempfile.TemporaryDirectory() as directory:
with socket.socket() as reservation:
reservation.bind(('127.0.0.1', 0))
port = reservation.getsockname()[1]
config = Path(directory) / 'config.json'
config.write_text(json.dumps({'server': '127.0.0.1', 'server_port': 9,
'local_address': '127.0.0.1', 'local_port': port, 'password': 'test-password',
'method': 'aes-128-gcm', 'mode': 'udp_only', 'ipv6_first': True}))
with tempfile.TemporaryFile(mode='w+') as log:
proc = subprocess.Popen([str(self.binaries['local']), '--config', str(config),
'--listen-port', str(port), '--verbose', *args], stdout=log, stderr=log)
try:
deadline = time.monotonic() + 5
connected = False
while time.monotonic() < deadline and proc.poll() is None:
try:
with socket.create_connection(('127.0.0.1', port), timeout=0.1):
connected = True
break
except OSError:
time.sleep(0.02)
self.assertTrue(connected, 'CLI TCP mode did not override UDP-only config')
finally:
if proc.poll() is None:
proc.terminate()
proc.wait(timeout=5)
log.seek(0)
output = log.read()
self.assertEqual('udprelay enabled' in output, udp, output)
self.assertEqual('resolving hostname to IPv6 address first' in output, ipv6, output)
def test_numeric_option_validation(self):
for name in self.binaries:
for value in ('0', '-1', 'abc', '2147483648'):
result = self.run_cli(name, '--timeout', value)
self.assertEqual(result.returncode, 2, (name, value))
self.assertEqual(result.stdout, '')
self.assertIn('positive integer', result.stderr)
if name != 'manager':
for value in ('0', '65536', '-1', 'abc'):
result = self.run_cli(name, '--listen-port', value)
self.assertEqual(result.returncode, 2, (name, value))
self.assertIn('1 to 65535', result.stderr)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--bin-dir', type=Path, default=BIN_DIR)
args = parser.parse_args()
BIN_DIR = args.bin_dir.resolve()
unittest.main(argv=[__file__])
+2 -2
View File
@@ -27,7 +27,7 @@ class CliDocsTests(unittest.TestCase):
def change(self, filename, old, new):
path = self.root / filename
source = path.read_text(encoding='utf-8')
self.assertIn(old, source)
self.assertTrue(old in source, f'Missing fixture text in {filename}')
path.write_text(source.replace(old, new), encoding='utf-8')
def test_current_documentation_matches_all_variants(self):
@@ -58,7 +58,7 @@ class CliDocsTests(unittest.TestCase):
DOCS.check(self.root)
def test_nonliteral_short_options_fail_closed(self):
self.change('src/local.c', '"f:s:p:l:k:t:m:i:c:b:a:n:huUv6A"', 'SHORT_OPTIONS')
self.change('src/local.c', '":f:s:p:l:k:t:m:i:c:b:a:n:huUv6A"', 'SHORT_OPTIONS')
with self.assertRaisesRegex(ValueError, 'literal getopt_long'):
DOCS.check(self.root)