Skip to content
This repository was archived by the owner on Jun 12, 2022. It is now read-only.

Commit 10fbb63

Browse files
committed
👁️ Server: Add -udpPreferIPv6
Sometimes clients connect to UDP targets using domain names, and since we can't do happy eyeballs with UDP, and Go's net.ResolveUDPAddr() prefers IPv4, this flag was added to get servers to prefer IPv6 for UDP domain targets.
1 parent 32bd3d6 commit 10fbb63

File tree

5 files changed

+78
-13
lines changed

5 files changed

+78
-13
lines changed

cmd/outline-ss-server/main.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type SSServer struct {
5555
blockPrivateNet bool
5656
listenerTFO bool
5757
dialerTFO bool
58+
udpPreferIPv6 bool
5859
}
5960

6061
func (s *SSServer) startPort(portNum int) (err error) {
@@ -78,7 +79,7 @@ func (s *SSServer) startPort(portNum int) (err error) {
7879
port := &ssPort{cipherList: service.NewCipherList()}
7980
// TODO: Register initial data metrics at zero.
8081
port.tcpService = service.NewTCPService(port.cipherList, &s.replayCache, s.saltPool, s.m, tcpReadTimeout, s.dialerTFO)
81-
port.udpService = service.NewUDPService(s.natTimeout, port.cipherList, s.m)
82+
port.udpService = service.NewUDPService(s.natTimeout, port.cipherList, s.m, s.udpPreferIPv6)
8283
if s.blockPrivateNet {
8384
port.tcpService.SetTargetIPValidator(onet.RequirePublicIP)
8485
port.udpService.SetTargetIPValidator(onet.RequirePublicIP)
@@ -162,7 +163,7 @@ func (s *SSServer) Stop() error {
162163
}
163164

164165
// RunSSServer starts a shadowsocks server running, and returns the server or an error.
165-
func RunSSServer(filename string, natTimeout time.Duration, sm metrics.ShadowsocksMetrics, replayHistory int, blockPrivateNet, listenerTFO, dialerTFO bool) (*SSServer, error) {
166+
func RunSSServer(filename string, natTimeout time.Duration, sm metrics.ShadowsocksMetrics, replayHistory int, blockPrivateNet, listenerTFO, dialerTFO, udpPreferIPv6 bool) (*SSServer, error) {
166167
server := &SSServer{
167168
natTimeout: natTimeout,
168169
m: sm,
@@ -172,6 +173,7 @@ func RunSSServer(filename string, natTimeout time.Duration, sm metrics.Shadowsoc
172173
blockPrivateNet: blockPrivateNet,
173174
listenerTFO: listenerTFO,
174175
dialerTFO: dialerTFO,
176+
udpPreferIPv6: udpPreferIPv6,
175177
}
176178
err := server.loadConfig(filename)
177179
if err != nil {
@@ -220,6 +222,7 @@ func main() {
220222
tfo bool
221223
listenerTFO bool
222224
dialerTFO bool
225+
udpPreferIPv6 bool
223226
ver bool
224227

225228
suppressTimestamps bool
@@ -240,6 +243,8 @@ func main() {
240243
flag.BoolVar(&listenerTFO, "tfoListener", false, "Enables TFO for TCP listener")
241244
flag.BoolVar(&dialerTFO, "tfoDialer", false, "Enables TFO for TCP dialer")
242245

246+
flag.BoolVar(&udpPreferIPv6, "udpPreferIPv6", false, "Prefer IPv6 addresses when resolving domain names for UDP targets")
247+
243248
flag.BoolVar(&suppressTimestamps, "suppressTimestamps", false, "Omit timestamps in logs")
244249
flag.StringVar(&logLevel, "logLevel", "info", "Set custom log level. Available levels: debug, info, warn, error, dpanic, panic, fatal")
245250

@@ -297,7 +302,7 @@ func main() {
297302
dialerTFO = true
298303
}
299304

300-
s, err := RunSSServer(configFile, natTimeout, m, replayHistory, blockPrivateNet, listenerTFO, dialerTFO)
305+
s, err := RunSSServer(configFile, natTimeout, m, replayHistory, blockPrivateNet, listenerTFO, dialerTFO, udpPreferIPv6)
301306
if err != nil {
302307
logger.Fatal("Failed to start Shadowsocks server", zap.Error(err))
303308
}

integration_test/integration_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ func TestUDPEcho(t *testing.T) {
258258
t.Fatal(err)
259259
}
260260
testMetrics := &fakeUDPMetrics{fakeLocation: "QQ"}
261-
proxy := service.NewUDPService(time.Hour, cipherList, testMetrics)
261+
proxy := service.NewUDPService(time.Hour, cipherList, testMetrics, true)
262262
go proxy.Serve(proxyConn)
263263

264264
client, err := client.NewClient(proxyConn.LocalAddr().String(), ss.TestCipher, secrets[0], nil)
@@ -345,7 +345,7 @@ func BenchmarkUDPEcho(b *testing.B) {
345345
if err != nil {
346346
b.Fatal(err)
347347
}
348-
proxy := service.NewUDPService(time.Hour, cipherList, &metrics.NoOpMetrics{})
348+
proxy := service.NewUDPService(time.Hour, cipherList, &metrics.NoOpMetrics{}, true)
349349
go proxy.Serve(proxyConn)
350350

351351
client, err := client.NewClient(proxyConn.LocalAddr().String(), ss.TestCipher, secrets[0], nil)
@@ -385,7 +385,7 @@ func BenchmarkUDPManyKeys(b *testing.B) {
385385
if err != nil {
386386
b.Fatal(err)
387387
}
388-
proxy := service.NewUDPService(time.Hour, cipherList, &metrics.NoOpMetrics{})
388+
proxy := service.NewUDPService(time.Hour, cipherList, &metrics.NoOpMetrics{}, true)
389389
go proxy.Serve(proxyConn)
390390

391391
var clients [numKeys]client.Client

service/udp.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,16 @@ type udpService struct {
5757
m metrics.ShadowsocksMetrics
5858
running sync.WaitGroup
5959
targetIPValidator onet.TargetIPValidator
60+
preferIPv6 bool
6061
}
6162

6263
// NewUDPService creates a UDPService
63-
func NewUDPService(natTimeout time.Duration, cipherList CipherList, m metrics.ShadowsocksMetrics) UDPService {
64+
func NewUDPService(natTimeout time.Duration, cipherList CipherList, m metrics.ShadowsocksMetrics, preferIPv6 bool) UDPService {
6465
return &udpService{
6566
natTimeout: natTimeout,
6667
ciphers: cipherList,
6768
m: m,
69+
preferIPv6: preferIPv6,
6870
}
6971
}
7072

@@ -389,7 +391,7 @@ func (s *udpService) validatePacket(textData []byte, cipherConfig ss.CipherConfi
389391
return nil, nil, onet.NewConnectionError("ERR_READ_HEADER", "Failed to read packet header", err)
390392
}
391393

392-
tgtUDPAddr, err := socksAddr.UDPAddr()
394+
tgtUDPAddr, err := socksAddr.UDPAddr(s.preferIPv6)
393395
if err != nil {
394396
logger.Warn("Failed to resolve UDPAddr",
395397
zap.Stringer("clientAddress", clientAddr),

service/udp_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func sendToDiscard(payloads [][]byte, validator onet.TargetIPValidator) *natTest
180180
cipher := ciphers.SnapshotForClientIP(nil)[0].Value.(*CipherEntry).Cipher
181181
clientConn := makePacketConn()
182182
metrics := &natTestMetrics{}
183-
service := NewUDPService(timeout, ciphers, metrics)
183+
service := NewUDPService(timeout, ciphers, metrics, true)
184184
service.SetTargetIPValidator(validator)
185185
go service.Serve(clientConn)
186186

@@ -539,7 +539,7 @@ func TestUDPDoubleServe(t *testing.T) {
539539
}
540540
testMetrics := &natTestMetrics{}
541541
const testTimeout = 200 * time.Millisecond
542-
s := NewUDPService(testTimeout, cipherList, testMetrics)
542+
s := NewUDPService(testTimeout, cipherList, testMetrics, true)
543543

544544
c := make(chan error)
545545
for i := 0; i < 2; i++ {
@@ -573,7 +573,7 @@ func TestUDPEarlyStop(t *testing.T) {
573573
}
574574
testMetrics := &natTestMetrics{}
575575
const testTimeout = 200 * time.Millisecond
576-
s := NewUDPService(testTimeout, cipherList, testMetrics)
576+
s := NewUDPService(testTimeout, cipherList, testMetrics, true)
577577

578578
if err := s.Stop(); err != nil {
579579
t.Error(err)

socks/socks.go

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package socks
22

33
import (
44
"encoding/binary"
5+
"errors"
56
"fmt"
67
"io"
8+
"math/rand"
79
"net"
810
"strconv"
911

@@ -100,10 +102,10 @@ func (a Addr) Addr(network string) (net.Addr, error) {
100102
}
101103
}
102104

103-
func (a Addr) UDPAddr() (*net.UDPAddr, error) {
105+
func (a Addr) UDPAddr(preferIPv6 bool) (*net.UDPAddr, error) {
104106
switch a[0] {
105107
case AtypDomainName:
106-
return net.ResolveUDPAddr("udp", a.String())
108+
return resolveUDPAddr(a.String(), preferIPv6)
107109
case AtypIPv4:
108110
return &net.UDPAddr{
109111
IP: net.IP(a[1 : 1+4]),
@@ -119,6 +121,62 @@ func (a Addr) UDPAddr() (*net.UDPAddr, error) {
119121
}
120122
}
121123

124+
// resolveUDPAddr resolves an address in domain:port format into *net.UDPAddr.
125+
// ip:port is not supported.
126+
func resolveUDPAddr(address string, preferIPv6 bool) (*net.UDPAddr, error) {
127+
if !preferIPv6 {
128+
return net.ResolveUDPAddr("udp", address)
129+
}
130+
131+
// Although !preferIPv6 was short-circuited to use net.ResolveUDPAddr(),
132+
// the following code is generic and takes preferIPv6 into account.
133+
134+
host, port, err := net.SplitHostPort(address)
135+
if err != nil {
136+
return nil, err
137+
}
138+
portNum, err := net.LookupPort("udp", port)
139+
if err != nil {
140+
return nil, err
141+
}
142+
ips, err := net.LookupIP(host)
143+
if err != nil {
144+
return nil, err
145+
}
146+
147+
// We can't actually do fallbacks here.
148+
// If preferIPv6 is true, v6 -> primaries, v4 -> fallbacks.
149+
// And vice versa.
150+
// Then we select a random IP from primaries, or fallbacks if primaries is empty.
151+
var primaries, fallbacks []net.IP
152+
153+
for _, ipc := range ips {
154+
ip4 := ipc.To4()
155+
switch {
156+
case preferIPv6 && ip4 == nil || !preferIPv6 && ip4 != nil: // Prefer 6/4 and got 6/4
157+
primaries = append(primaries, ipc)
158+
case preferIPv6 && ip4 != nil || !preferIPv6 && ip4 == nil: // Prefer 6/4 and got 4/6
159+
fallbacks = append(fallbacks, ipc)
160+
}
161+
}
162+
163+
var ip net.IP
164+
165+
switch {
166+
case len(primaries) > 0:
167+
ip = primaries[rand.Intn(len(primaries))]
168+
case len(fallbacks) > 0:
169+
ip = fallbacks[rand.Intn(len(fallbacks))]
170+
default:
171+
return nil, errors.New("lookup returned no addresses and no error")
172+
}
173+
174+
return &net.UDPAddr{
175+
IP: ip,
176+
Port: portNum,
177+
}, nil
178+
}
179+
122180
// WriteAddr parses an address string into a socks address
123181
// and writes to the destination slice.
124182
//

0 commit comments

Comments
 (0)