diff --git a/doc/shadowsocks-libev.asciidoc b/doc/shadowsocks-libev.asciidoc index f97aeb9f..13cddc87 100644 --- a/doc/shadowsocks-libev.asciidoc +++ b/doc/shadowsocks-libev.asciidoc @@ -103,10 +103,10 @@ Not available in redir mode. -b :: Specify the local address to bind. + -For servers: Specify the local address to use while this server is making +For servers: Specify the local address to use while this server is making outbound connections to remote servers on behalf of the clients. + -For clients: Specify the local address to use while this client is making +For clients: Specify the local address to use while this client is making outbound connections to the server. + Not available in manager mode. @@ -178,6 +178,8 @@ The config file equivalent of command line options is listed as example below. | -s some.server.net -p 1234 (client) | "server": "some.server.net:1234" | -p 1234 | "server_port": "1234" | -b 0.0.0.0 | "local_address": "0.0.0.0" +| -b 10.0.0.2 | "local_ipv4_address": "10.0.0.2" +| -b 2620:129:35::33 | "local_ipv6_address": "2620:129:35::33" | -l 4321 | "local_port": "4321" | -k "PasSworD" | "password": "PasSworD" | -m "aes-256-cfb" | "method": "aes-256-cfb" diff --git a/src/jconf.c b/src/jconf.c index 2335d443..b843104b 100644 --- a/src/jconf.c +++ b/src/jconf.c @@ -241,6 +241,10 @@ read_jconf(const char *file) conf.remote_port = to_string(value); } else if (strcmp(name, "local_address") == 0) { conf.local_addr = to_string(value); + } else if (strcmp(name, "local_ipv4_address") == 0) { + conf.local_addr_v4 = to_string(value); + } else if (strcmp(name, "local_ipv6_address") == 0) { + conf.local_addr_v6 = to_string(value); } else if (strcmp(name, "local_port") == 0) { conf.local_port = to_string(value); } else if (strcmp(name, "password") == 0) { diff --git a/src/jconf.h b/src/jconf.h index a003b5c4..9a2209b5 100644 --- a/src/jconf.h +++ b/src/jconf.h @@ -60,6 +60,8 @@ typedef struct { ss_port_password_t port_password[MAX_PORT_NUM]; char *remote_port; char *local_addr; + char *local_addr_v4; + char *local_addr_v6; char *local_port; char *password; char *key; diff --git a/src/netutils.c b/src/netutils.c index 2eb1edc9..895ea753 100644 --- a/src/netutils.c +++ b/src/netutils.c @@ -86,32 +86,45 @@ setinterface(int socket_fd, const char *interface_name) #endif int -bind_to_address(int socket_fd, const char *host) +parse_local_addr(struct sockaddr_storage *storage_v4, + struct sockaddr_storage *storage_v6, + const char *host) { - static struct sockaddr_storage storage = { 0 }; - if (storage.ss_family == AF_INET) { - return bind(socket_fd, (struct sockaddr *)&storage, sizeof(struct sockaddr_in)); - } else if (storage.ss_family == AF_INET6) { - return bind(socket_fd, (struct sockaddr *)&storage, sizeof(struct sockaddr_in6)); - } else if (host != NULL) { + if (host != NULL) { struct cork_ip ip; if (cork_ip_init(&ip, host) != -1) { if (ip.version == 4) { - struct sockaddr_in *addr = (struct sockaddr_in *)&storage; + memset(storage_v4, 0, sizeof(struct sockaddr_storage)); + struct sockaddr_in *addr = (struct sockaddr_in *)storage_v4; inet_pton(AF_INET, host, &addr->sin_addr); addr->sin_family = AF_INET; - return bind(socket_fd, (struct sockaddr *)addr, sizeof(struct sockaddr_in)); + LOGI("binding to outbound IPv4 addr: %s", host); + return 0; } else if (ip.version == 6) { - struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&storage; + memset(storage_v6, 0, sizeof(struct sockaddr_storage)); + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)storage_v6; inet_pton(AF_INET6, host, &addr->sin6_addr); addr->sin6_family = AF_INET6; - return bind(socket_fd, (struct sockaddr *)addr, sizeof(struct sockaddr_in6)); + LOGI("binding to outbound IPv6 addr: %s", host); + return 0; } } } return -1; } +int +bind_to_addr(struct sockaddr_storage *storage, + int socket_fd) +{ + if (storage->ss_family == AF_INET) { + return bind(socket_fd, (struct sockaddr *)storage, sizeof(struct sockaddr_in)); + } else if (storage->ss_family == AF_INET6) { + return bind(socket_fd, (struct sockaddr *)storage, sizeof(struct sockaddr_in6)); + } + return -1; +} + ssize_t get_sockaddr(char *host, char *port, struct sockaddr_storage *storage, int block, diff --git a/src/netutils.h b/src/netutils.h index a4b19884..ee62317e 100644 --- a/src/netutils.h +++ b/src/netutils.h @@ -88,7 +88,11 @@ int set_reuseport(int socket); int setinterface(int socket_fd, const char *interface_name); #endif -int bind_to_address(int socket_fd, const char *address); +int parse_local_addr(struct sockaddr_storage *storage_v4, + struct sockaddr_storage *storage_v6, + const char *host); + +int bind_to_addr(struct sockaddr_storage *storage, int socket_fd); /** * Compare two sockaddrs. Imposes an ordering on the addresses. diff --git a/src/server.c b/src/server.c index 38d3a9f4..21ec535a 100644 --- a/src/server.c +++ b/src/server.c @@ -109,7 +109,10 @@ static void resolv_free_cb(void *data); int verbose = 0; int reuse_port = 0; -char *local_addr = NULL; + +int is_bind_local_addr = 0; +struct sockaddr_storage local_addr_v4; +struct sockaddr_storage local_addr_v6; static crypto_t *crypto; @@ -496,12 +499,16 @@ connect_to_remote(EV_P_ struct addrinfo *res, if (setnonblocking(sockfd) == -1) ERROR("setnonblocking"); - if (local_addr != NULL) - if (bind_to_address(sockfd, local_addr) == -1) { - ERROR("bind_to_address"); + if (is_bind_local_addr) + { + struct sockaddr_storage *local_addr = + res->ai_family == AF_INET ? &local_addr_v4 : &local_addr_v6; + if (bind_to_addr(local_addr, sockfd) == -1) { + ERROR("bind_to_addr"); close(sockfd); return NULL; } + } #ifdef SET_INTERFACE if (iface) { @@ -1678,7 +1685,8 @@ main(int argc, char **argv) } break; case 'b': - local_addr = optarg; + if (parse_local_addr(&local_addr_v4, &local_addr_v6, optarg) == 0) + is_bind_local_addr = 1; break; case 'p': server_port = optarg; @@ -1801,8 +1809,13 @@ main(int argc, char **argv) if (fast_open == 0) { fast_open = conf->fast_open; } - if (local_addr == NULL) { - local_addr = conf->local_addr; + if (is_bind_local_addr == 0) { + if (parse_local_addr(&local_addr_v4, &local_addr_v6, conf->local_addr) == 0) + is_bind_local_addr = 1; + if (parse_local_addr(&local_addr_v4, &local_addr_v6, conf->local_addr_v4) == 0) + is_bind_local_addr = 1; + if (parse_local_addr(&local_addr_v4, &local_addr_v6, conf->local_addr_v6) == 0) + is_bind_local_addr = 1; } #ifdef HAVE_SETRLIMIT if (nofile == 0) { diff --git a/src/udprelay.c b/src/udprelay.c index b58ee130..91d6f330 100644 --- a/src/udprelay.c +++ b/src/udprelay.c @@ -98,7 +98,10 @@ extern int reuse_port; #ifdef MODULE_REMOTE extern uint64_t tx; extern uint64_t rx; -extern char *local_addr; + +extern int is_bind_local_addr; +extern struct sockaddr_storage local_addr_v4; +extern struct sockaddr_storage local_addr_v6; #endif static int packet_size = DEFAULT_PACKET_SIZE; @@ -371,9 +374,9 @@ create_remote_socket(int ipv6) return -1; } #ifdef MODULE_REMOTE - if (local_addr != NULL) { - if (bind_to_address(remote_sock, local_addr) == -1) { - ERROR("bind_to_address"); + if (is_bind_local_addr) { + if (bind_to_addr(&local_addr_v6, remote_sock) == -1) { + ERROR("bind_to_addr"); FATAL("[udp] cannot bind remote"); return -1; } @@ -399,9 +402,9 @@ create_remote_socket(int ipv6) return -1; } #ifdef MODULE_REMOTE - if (local_addr != NULL) { - if (bind_to_address(remote_sock, local_addr) == -1) { - ERROR("bind_to_address"); + if (is_bind_local_addr) { + if (bind_to_addr(&local_addr_v4, remote_sock) == -1) { + ERROR("bind_to_addr"); FATAL("[udp] cannot bind remote"); return -1; }