mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-29 20:43:44 +00:00
Accept combined server endpoints with strict IPv6 parsing
This commit is contained in:
+86
@@ -135,6 +135,92 @@ parse_addr(const char *str_in, ss_addr_t *addr)
|
||||
free(str);
|
||||
}
|
||||
|
||||
/* CLI endpoints are strict; keep the legacy JSON address parser unchanged. */
|
||||
int
|
||||
parse_server_endpoint(const char *str, ss_addr_t *addr)
|
||||
{
|
||||
ss_addr_t parsed = { 0 };
|
||||
uint16_t port;
|
||||
if (str == NULL || *str == '\0')
|
||||
return -1;
|
||||
for (const char *p = str; *p; p++) {
|
||||
if ((unsigned char)*p <= ' ' || *p == '/' || *p == '@')
|
||||
return -1;
|
||||
}
|
||||
parse_addr(str, &parsed);
|
||||
if (parsed.host == NULL || *parsed.host == '\0')
|
||||
goto invalid;
|
||||
if (str[0] == '[') {
|
||||
const char *end = strchr(str, ']');
|
||||
if (end == NULL || (end[1] != '\0' && end[1] != ':')
|
||||
|| (end[1] == ':' && end[2] == '\0'))
|
||||
goto invalid;
|
||||
} else if (strchr(str, '[') || strchr(str, ']')) {
|
||||
goto invalid;
|
||||
}
|
||||
if (str[0] == '[' || strchr(parsed.host, ':')) {
|
||||
/* Validate IPv6 separately from an optional interface scope ID. */
|
||||
struct ss_ip ip;
|
||||
char *literal = strdup(parsed.host);
|
||||
char *scope = strchr(literal, '%');
|
||||
int valid_scope = 1;
|
||||
if (scope != NULL) {
|
||||
*scope++ = '\0';
|
||||
valid_scope = *scope != '\0';
|
||||
for (const char *p = scope; *p; p++) {
|
||||
if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
|
||||
|| (*p >= '0' && *p <= '9') || *p == '_' || *p == '-' || *p == '.'))
|
||||
valid_scope = 0;
|
||||
}
|
||||
}
|
||||
int valid = strchr(literal, ':') != NULL && ss_ip_init(&ip, literal) != -1;
|
||||
free(literal);
|
||||
if (!valid || !valid_scope)
|
||||
goto invalid;
|
||||
} else if (strchr(parsed.host, '%')) {
|
||||
goto invalid;
|
||||
}
|
||||
if (parsed.port != NULL) {
|
||||
if (ss_parse_uint16_port(parsed.port, &port) != 0)
|
||||
goto invalid;
|
||||
for (const char *p = parsed.port; *p; p++) {
|
||||
if (*p < '0' || *p > '9')
|
||||
goto invalid;
|
||||
}
|
||||
} else if (str[strlen(str) - 1] == ':' && !strchr(parsed.host, ':')) {
|
||||
goto invalid;
|
||||
}
|
||||
*addr = parsed;
|
||||
return 0;
|
||||
invalid:
|
||||
free_addr(&parsed);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Resolve fallback ports before callers reuse the first port for SIP003. */
|
||||
int
|
||||
complete_server_ports(ss_addr_t *addr, int count, const char *fallback, int plugin)
|
||||
{
|
||||
uint16_t first = 0;
|
||||
if (count == 0)
|
||||
return -1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint16_t port;
|
||||
const char *value = addr[i].port != NULL ? addr[i].port : fallback;
|
||||
if (value == NULL || ss_parse_uint16_port(value, &port) != 0)
|
||||
return -1;
|
||||
if (plugin && i > 0 && port != first)
|
||||
return -1;
|
||||
if (i == 0)
|
||||
first = port;
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (addr[i].port == NULL)
|
||||
addr[i].port = strdup(fallback);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_dscp(char *str)
|
||||
{
|
||||
|
||||
@@ -95,5 +95,7 @@ typedef struct {
|
||||
jconf_t *read_jconf(const char *file);
|
||||
void parse_addr(const char *str, ss_addr_t *addr);
|
||||
void free_addr(ss_addr_t *addr);
|
||||
int parse_server_endpoint(const char *str, ss_addr_t *addr);
|
||||
int complete_server_ports(ss_addr_t *addr, int count, const char *fallback, int plugin);
|
||||
|
||||
#endif // _JCONF_H
|
||||
|
||||
+12
-4
@@ -1563,13 +1563,13 @@ 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`.
|
||||
\par `--server <HOST:PORT>`
|
||||
Set a remote server endpoint; may be repeated with different ports. Use `HOST:PORT` for a hostname or IPv4, and `[IPv6]:PORT` for IPv6 (including `%scope` when needed). Bare IPv6 is always a host, never split into a port. Host-only values require the legacy `--server-port` fallback or a configured port. An endpoint port takes priority over the fallback regardless of option order. SIP003 plugins require the same port for all endpoints. Short alias: `-s`.
|
||||
[cli_long_server] */
|
||||
|
||||
/* [cli_long_server_port]
|
||||
\par `--server-port <server_port>`
|
||||
Set the remote Shadowsocks server port. Short alias: `-p`.
|
||||
Legacy fallback port for server values without an embedded port. Prefer `--server HOST:PORT`. Short alias: `-p`.
|
||||
[cli_long_server_port] */
|
||||
|
||||
/* [cli_long_listen_address]
|
||||
@@ -1802,7 +1802,11 @@ Same behavior as `-S`; see that option for details. Short alias: `-S`.
|
||||
break;
|
||||
case 's':
|
||||
if (remote_num < MAX_REMOTE_NUM) {
|
||||
parse_addr(optarg, &remote_addr[remote_num++]);
|
||||
if (parse_server_endpoint(optarg, &remote_addr[remote_num]) != 0)
|
||||
cli_error("invalid server endpoint; use HOST:PORT or [IPv6]:PORT", 's', NULL);
|
||||
remote_num++;
|
||||
} else {
|
||||
cli_error("too many server endpoints", 's', NULL);
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
@@ -1991,6 +1995,10 @@ Same behavior as `-S`; see that option for details. Short alias: `-S`.
|
||||
}
|
||||
}
|
||||
|
||||
if (complete_server_ports(remote_addr, remote_num, remote_port, plugin != NULL) != 0)
|
||||
cli_error("each server needs a port (1 to 65535); plugins require a shared port", 's', NULL);
|
||||
remote_port = remote_addr[0].port;
|
||||
|
||||
if (remote_num == 0) {
|
||||
fprintf(stderr, "remote_num is 0\n");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
+12
-4
@@ -978,13 +978,13 @@ 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`.
|
||||
\par `--server <HOST:PORT>`
|
||||
Set a remote server endpoint; may be repeated with different ports. Use `HOST:PORT` for a hostname or IPv4, and `[IPv6]:PORT` for IPv6 (including `%scope` when needed). Bare IPv6 is always a host, never split into a port. Host-only values require the legacy `--server-port` fallback or a configured port. An endpoint port takes priority over the fallback regardless of option order. SIP003 plugins require the same port for all endpoints. Short alias: `-s`.
|
||||
[cli_long_server] */
|
||||
|
||||
/* [cli_long_server_port]
|
||||
\par `--server-port <server_port>`
|
||||
Set the remote Shadowsocks server port. Short alias: `-p`.
|
||||
Legacy fallback port for server values without an embedded port. Prefer `--server HOST:PORT`. Short alias: `-p`.
|
||||
[cli_long_server_port] */
|
||||
|
||||
/* [cli_long_listen_address]
|
||||
@@ -1162,7 +1162,11 @@ Same behavior as `-T`; see that option for details. Short alias: `-T`.
|
||||
break;
|
||||
case 's':
|
||||
if (remote_num < MAX_REMOTE_NUM) {
|
||||
parse_addr(optarg, &remote_addr[remote_num++]);
|
||||
if (parse_server_endpoint(optarg, &remote_addr[remote_num]) != 0)
|
||||
cli_error("invalid server endpoint; use HOST:PORT or [IPv6]:PORT", 's', NULL);
|
||||
remote_num++;
|
||||
} else {
|
||||
cli_error("too many server endpoints", 's', NULL);
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
@@ -1345,6 +1349,10 @@ Same behavior as `-T`; see that option for details. Short alias: `-T`.
|
||||
dscp = conf->dscp;
|
||||
}
|
||||
|
||||
if (complete_server_ports(remote_addr, remote_num, remote_port, plugin != NULL) != 0)
|
||||
cli_error("each server needs a port (1 to 65535); plugins require a shared port", 's', NULL);
|
||||
remote_port = remote_addr[0].port;
|
||||
|
||||
if (remote_num == 0 || remote_port == NULL || local_port == NULL
|
||||
|| (password == NULL && key == NULL)) {
|
||||
usage();
|
||||
|
||||
+12
-4
@@ -1008,13 +1008,13 @@ 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`.
|
||||
\par `--server <HOST:PORT>`
|
||||
Set a remote server endpoint; may be repeated with different ports. Use `HOST:PORT` for a hostname or IPv4, and `[IPv6]:PORT` for IPv6 (including `%scope` when needed). Bare IPv6 is always a host, never split into a port. Host-only values require the legacy `--server-port` fallback or a configured port. An endpoint port takes priority over the fallback regardless of option order. SIP003 plugins require the same port for all endpoints. Short alias: `-s`.
|
||||
[cli_long_server] */
|
||||
|
||||
/* [cli_long_server_port]
|
||||
\par `--server-port <server_port>`
|
||||
Set the remote Shadowsocks server port. Short alias: `-p`.
|
||||
Legacy fallback port for server values without an embedded port. Prefer `--server HOST:PORT`. Short alias: `-p`.
|
||||
[cli_long_server_port] */
|
||||
|
||||
/* [cli_long_listen_address]
|
||||
@@ -1215,7 +1215,11 @@ Same behavior as `-V`; see that option for details. Short alias: `-V`.
|
||||
break;
|
||||
case 's':
|
||||
if (remote_num < MAX_REMOTE_NUM) {
|
||||
parse_addr(optarg, &remote_addr[remote_num++]);
|
||||
if (parse_server_endpoint(optarg, &remote_addr[remote_num]) != 0)
|
||||
cli_error("invalid server endpoint; use HOST:PORT or [IPv6]:PORT", 's', NULL);
|
||||
remote_num++;
|
||||
} else {
|
||||
cli_error("too many server endpoints", 's', NULL);
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
@@ -1404,6 +1408,10 @@ Same behavior as `-V`; see that option for details. Short alias: `-V`.
|
||||
#endif
|
||||
}
|
||||
|
||||
if (complete_server_ports(remote_addr, remote_num, remote_port, plugin != NULL) != 0)
|
||||
cli_error("each server needs a port (1 to 65535); plugins require a shared port", 's', NULL);
|
||||
remote_port = remote_addr[0].port;
|
||||
|
||||
if (remote_num == 0 || remote_port == NULL || tunnel_addr_str == NULL
|
||||
|| local_port == NULL || (password == NULL && key == NULL)) {
|
||||
usage();
|
||||
|
||||
+4
-4
@@ -319,12 +319,12 @@ ss_is_ipv6addr(const char *addr)
|
||||
|
||||
/* [cli_short_s]
|
||||
\par `-s <server_host>`
|
||||
Set the server's hostname or IP.
|
||||
For clients, set a server endpoint as `HOST:PORT` or `[IPv6]:PORT`; host-only values use the legacy `-p` or configured fallback. Bare IPv6 is never split at its last colon. For server and manager, set the listening address.
|
||||
[cli_short_s] */
|
||||
|
||||
/* [cli_short_p]
|
||||
\par `-p <server_port>`
|
||||
Set the server's port number.
|
||||
For clients, set the legacy fallback port for server values without an embedded port. For ss-server, set the listening port.
|
||||
[cli_short_p] */
|
||||
|
||||
/* [cli_short_l]
|
||||
@@ -638,8 +638,8 @@ usage(void)
|
||||
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("-s, --server HOST:PORT", "Remote endpoint; IPv6: [ADDRESS]:PORT. Repeatable.");
|
||||
cli_help_option("-p, --server-port PORT", "Legacy fallback for servers without a port.");
|
||||
cli_help_option("-b, --listen-address ADDRESS", "Local address to bind.");
|
||||
cli_help_option("-l, --listen-port PORT", "Local listening port.");
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user