shadowsocks / v2ray-plugin

A SIP003 plugin based on v2ray
MIT License
2.68k stars 571 forks source link

Broken dual stack UDP forwarding #119

Open kotarou3 opened 5 years ago

kotarou3 commented 5 years ago

According to #113, I am not meant to use "server": ["::", "0.0.0.0"], and am meant to use either only :: or 0.0.0.0 (or leave it out completely). This works fine for TCP forwarding on both IPv4 and IPv6, but breaks dual stack UDP forwarding.

Consider the cases:

If I don't use v2ray-plugin, I can use "server": ["::", "0.0.0.0"] and it works fine, so that feels like the intended way to enable dual stack UDP relay (https://github.com/shadowsocks/shadowsocks-libev/issues/1349). But since enabling v2ray-plugin causes the "bind: address already in use" error (#113), I'm reporting the bug here.

The root cause seems to be v2ray-plugin trying to bind to [::]:<port> twice (in my case, <port> is 443):

$ sudo strace -fe trace=%net ss-server -c /etc/shadowsocks-libev/config.json -s :: -s 0.0.0.0

...snip...

2019/09/01 17:25:17 V2Ray 4.19.1 (Po) Custom
2019/09/01 17:25:17 A unified platform for anti-censorship.
strace: Process 5987 attached
strace: Process 5989 attached
strace: Process 5990 attached
strace: Process 5991 attached
[pid  5991] socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 3
[pid  5991] socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 3
[pid  5991] setsockopt(3, SOL_IPV6, IPV6_V6ONLY, [1], 4) = 0
[pid  5991] bind(3, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
[pid  5991] socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 6
[pid  5991] setsockopt(6, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
[pid  5991] bind(6, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
[pid  5991] socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
[pid  5991] setsockopt(3, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
[pid  5991] setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
[pid  5991] setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
[pid  5991] bind(3, {sa_family=AF_INET6, sin6_port=htons(443), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
[pid  5991] listen(3, 128)              = 0
[pid  5991] getsockname(3, {sa_family=AF_INET6, sin6_port=htons(443), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [112->28]) = 0
[pid  5991] socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 6
[pid  5991] setsockopt(6, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
[pid  5991] setsockopt(6, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
[pid  5991] setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
[pid  5991] bind(6, {sa_family=AF_INET6, sin6_port=htons(443), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = -1 EADDRINUSE (Address already in use)
2019/09/01 17:25:17 failed to start server: v2ray.com/core/app/proxyman/inbound: failed to listen TCP on 443 > v2ray.com/core/transport/internet: failed to listen on address: 0.0.0.0:443 > v2ray.com/core/transport/internet/websocket: failed to listen TCP on0.0.0.0:443 > listen tcp 0.0.0.0:443: bind: address already in use
icpz commented 5 years ago

Golang's socket implementation listens on dual stack address when listens on "::" or "0.0.0.0", and it cannot be hacked.

A workaround is that you could listen on your server's public ip address, e.g.

{
    "server":["189.1.2.3","2001:x:x::xx:x"], <--- your public v4 and v6 address
    "server_port":6666,
    "local_port":1080,
    "password":"password",
    "timeout":60,
    "method":"chacha20-ietf-poly1305",
    "mode":"tcp_and_udp",
    "fast_open": true,
    "plugin":"/usr/local/bin/v2ray-plugin",
    "plugin_opts":"server"
}

This could prevent go's runtime from listening both v4/v6 on same address.

@madeye Do you think it's necessary to check the -localAddr option and remove the duplicate :: or 0.0.0.0? Seems very dirty...

Mygod commented 4 years ago

You can set "server": "::".

https://unix.stackexchange.com/a/152618/145080

kotarou3 commented 4 years ago

Was this a recent change? Since I mentioned before that it doesn't work

Mygod commented 4 years ago

It seems like a bug on shadowsocks-libev.

madeye commented 4 years ago

In shadowsocks-libev, ::0 means binding to all IPv6 addresses.

The design choice here is that we want to allow the user bind to all IPv6 addresses and one IPv4 address. If we bind ::0 to all addresses including IPv4 and IPv6, it's impossible anymore.

However, golang application bind even 0.0.0.0 to both IPv4 and IPv6, which is causing the conflict here.

A workaround is starting another shadowsocks-libev process to work in UDP relay only mode.

Mygod commented 4 years ago

@madeye Well there are "IPv4-mapped IPv6 addresses" so...