Răsfoiți Sursa

Add Conjure Prefix and DTLS transports

- Add port randomization to Min transport

- Add Prefix and DTLS transports, with port randomization

- Drop Obsf4 transport

- Enable Conjure IPv6 dials

- Add new tactics for Conjure transport selection and for DTLS transport STUN
  server address configuration

- Update embedded Conjure ClientConf

- Fully disable TAPDANCE-OSSH

- NewUDPConn: add dial mode and IPv4/IPv6 address specification

- Refactor WriteTimeoutUDPConn for common use

- Add ResolveParameters.PreresolvedDomain, allowing for one ResolveParameters
  to be shared for multiple domain resolutions

- ObfuscatedSSHConn: skip 0-byte writes, just in case those trigger unintended
  underlying transport traffic
Rod Hynes 2 ani în urmă
părinte
comite
05f0df66ac

+ 35 - 33
go.mod

@@ -2,17 +2,9 @@ module github.com/Psiphon-Labs/psiphon-tunnel-core
 
 go 1.19
 
-// When this is the main module, use a patched version of
-// refraction/gotapdance with
-// https://github.com/Psiphon-Labs/psiphon-tunnel-core/commit/2a4121d9
-replace github.com/refraction-networking/gotapdance => ./replace/gotapdance
+replace gitlab.com/yawning/obfs4.git => github.com/jmwample/obfs4 v0.0.0-20230725223418-2d2e5b4a16ba
 
-// When this is the main module, gitlab.com/yawning/obfs4, used by
-// refraction-networking/gotapdance, is pinned at 816cff15 the last revision
-// published without a GPL license. This version lacks obfuscation
-// improvements added in revision 1a6129b6, but these changes apply only on
-// the server side.
-replace gitlab.com/yawning/obfs4.git => ./replace/obfs4.git
+replace github.com/pion/dtls/v2 => github.com/mingyech/dtls/v2 v2.0.0
 
 require (
 	github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e
@@ -31,63 +23,73 @@ require (
 	github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0
 	github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
-	github.com/google/gopacket v1.1.19-0.20200831200443-df1bbd09a561
+	github.com/google/gopacket v1.1.19
 	github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427
-	github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47
+	github.com/hashicorp/golang-lru v1.0.2
 	github.com/juju/ratelimit v1.0.2
 	github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a
 	github.com/miekg/dns v1.1.44-0.20210804161652-ab67aa642300
 	github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557
-	github.com/oschwald/maxminddb-golang v1.2.1-0.20170901134056-26fe5ace1c70
+	github.com/oschwald/maxminddb-golang v1.12.0
 	github.com/patrickmn/go-cache v2.1.0+incompatible
-	github.com/refraction-networking/gotapdance v1.2.0
+	github.com/refraction-networking/conjure v0.7.8-0.20231019174926-d2f991831312
+	github.com/refraction-networking/gotapdance v1.7.7
 	github.com/refraction-networking/utls v1.3.3
 	github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735
-	github.com/sirupsen/logrus v1.8.1
-	github.com/stretchr/testify v1.7.1
+	github.com/sirupsen/logrus v1.9.3
+	github.com/stretchr/testify v1.8.4
 	github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8
 	github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78
-	golang.org/x/crypto v0.10.0
-	golang.org/x/net v0.11.0
+	golang.org/x/crypto v0.14.0
+	golang.org/x/net v0.17.0
 	golang.org/x/sync v0.1.0
-	golang.org/x/sys v0.9.0
-	golang.org/x/term v0.9.0
+	golang.org/x/sys v0.13.0
+	golang.org/x/term v0.13.0
 )
 
 require (
-	git.torproject.org/pluggable-transports/goptlib.git v1.2.0 // indirect
 	github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57 // indirect
-	github.com/BurntSushi/toml v0.3.1 // indirect
+	github.com/BurntSushi/toml v1.3.2 // indirect
 	github.com/Psiphon-Labs/qtls-go1-19 v0.0.0-20230608213623-d58aa73e519a // indirect
 	github.com/Psiphon-Labs/qtls-go1-20 v0.0.0-20230608214729-dd57d6787acf // indirect
-	github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
 	github.com/andybalholm/brotli v1.0.5 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/dchest/siphash v1.2.3-0.20201109081723-a21c2e7914a8 // indirect
+	github.com/dchest/siphash v1.2.3 // indirect
 	github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 // indirect
-	github.com/gaukas/godicttls v0.0.3 // indirect
+	github.com/flynn/noise v1.0.0 // indirect
+	github.com/gaukas/godicttls v0.0.4 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/golang/mock v1.6.0 // indirect
-	github.com/golang/protobuf v1.5.3-0.20210916003710-5d5e8c018a13 // indirect
-	github.com/google/go-cmp v0.5.8 // indirect
-	github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/google/go-cmp v0.5.9 // indirect
+	github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
 	github.com/josharian/native v1.0.0 // indirect
 	github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
-	github.com/klauspost/compress v1.16.6 // indirect
+	github.com/klauspost/compress v1.16.7 // indirect
+	github.com/libp2p/go-reuseport v0.4.0 // indirect
 	github.com/mdlayher/netlink v1.4.2-0.20210930205308-a81a8c23d40a // indirect
 	github.com/mdlayher/socket v0.0.0-20210624160740-9dbe287ded84 // indirect
-	github.com/mroth/weightedrand v0.4.1 // indirect
+	github.com/mroth/weightedrand v1.0.0 // indirect
 	github.com/onsi/ginkgo/v2 v2.2.0 // indirect
+	github.com/pelletier/go-toml v1.9.5 // indirect
+	github.com/pion/dtls/v2 v2.2.7 // indirect
+	github.com/pion/logging v0.2.2 // indirect
+	github.com/pion/randutil v0.1.0 // indirect
+	github.com/pion/sctp v1.8.8 // indirect
+	github.com/pion/stun v0.6.1 // indirect
+	github.com/pion/transport/v2 v2.2.3 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/quic-go/qpack v0.4.0 // indirect
+	github.com/refraction-networking/ed25519 v0.1.2 // indirect
+	github.com/refraction-networking/obfs4 v0.1.2 // indirect
 	github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 // indirect
-	gitlab.com/yawning/obfs4.git v0.0.0-20190120164510-816cff15f425 // indirect
+	gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 // indirect
 	golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 // indirect
 	golang.org/x/mod v0.8.0 // indirect
-	golang.org/x/text v0.10.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
 	golang.org/x/tools v0.6.0 // indirect
-	google.golang.org/protobuf v1.28.0 // indirect
+	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	honnef.co/go/tools v0.2.1 // indirect
 )

+ 129 - 64
go.sum

@@ -1,9 +1,8 @@
-git.torproject.org/pluggable-transports/goptlib.git v1.2.0 h1:0qRF7Dw5qXd0FtZkjWUiAh5GTutRtDGL4GXUDJ4qMHs=
-git.torproject.org/pluggable-transports/goptlib.git v1.2.0/go.mod h1:4PBMl1dg7/3vMWSoWb46eGWlrxkUyn/CAJmxhDLAlDs=
 github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57 h1:CVuXDbdzPW0XCNYTldy5dQues57geAs+vfwz3FTTpy8=
 github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
+github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e h1:NPfqIbzmijrl0VclX2t8eO5EPBhqe47LLGKpRrcVjXk=
 github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e/go.mod h1:ZdY5pBfat/WVzw3eXbIf7N1nZN0XD5H5+X8ZMDWbCs4=
 github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 h1:Hx/NCZTnvoKZuIBwSmxE58KKoNLXIGG6hBJYN7pj9Ag=
@@ -18,14 +17,13 @@ github.com/Psiphon-Labs/quic-go v0.0.0-20230626192210-73f29effc9da h1:TI2+ExyFR3
 github.com/Psiphon-Labs/quic-go v0.0.0-20230626192210-73f29effc9da/go.mod h1:wTIxqsKVrEQIxVIIYOEHuscY+PM3h6Wz79u5aF60fo0=
 github.com/Psiphon-Labs/tls-tris v0.0.0-20230824155421-58bf6d336a9a h1:BOfU6ghaMsT/c40sWHmf3PXNwIendYXzL6tRv6NbPog=
 github.com/Psiphon-Labs/tls-tris v0.0.0-20230824155421-58bf6d336a9a/go.mod h1:v3y9GXFo9Sf2mO6auD2ExGG7oDgrK8TI7eb49ZnUxrE=
-github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
-github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
 github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
 github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f h1:SaJ6yqg936TshyeFZqQE+N+9hYkIeL9AMr7S4voCl10=
 github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
 github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61 h1:BU+NxuoaYPIvvp8NNkNlLr8aA0utGyuunf4Q3LJ0bh0=
 github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 h1:a1zrFsLFac2xoM6zG1u72DWJwZG3ayttYLfmLbxVETk=
 github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -38,14 +36,15 @@ github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea/go.mod h1:M
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dchest/siphash v1.2.3-0.20201109081723-a21c2e7914a8 h1:/3Ns/G7byyjzX76ElLmWJzmr4Ln2xAJy5mXdyowmGck=
-github.com/dchest/siphash v1.2.3-0.20201109081723-a21c2e7914a8/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
+github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
+github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
 github.com/deckarep/golang-set v0.0.0-20171013212420-1d4478f51bed h1:njG8LmGD6JCWJu4bwIKmkOHvch70UOEIqczl5vp7Gok=
 github.com/deckarep/golang-set v0.0.0-20171013212420-1d4478f51bed/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
 github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273 h1:45qZ7jowabqhyi3l9Ervox4dhQvLGB5BJPdC8w0a77k=
 github.com/dgraph-io/badger v1.5.4-0.20180815194500-3a87f6d9c273/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
 github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 h1:afESQBXJEnj3fu+34X//E8Wg3nEbMJxJkwSc0tPePK0=
 github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d h1:rtM8HsT3NG37YPjz8sYSbUSdElP9lUsQENYzJDZDUBE=
 github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
@@ -53,9 +52,12 @@ github.com/elazarl/goproxy/ext v0.0.0-20200809112317-0581fc3aee2d h1:st1tmvy+4du
 github.com/elazarl/goproxy/ext v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
 github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0 h1:7ZJyJV4KiWBijCCzUPvVaqxsDxO36+KD0XKBdEN3I+8=
 github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0/go.mod h1:2z3Tfqwv2ueuK6h563xUHRcCh1mv38wS9EjiWiesk84=
+github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
+github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
-github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
-github.com/gaukas/godicttls v0.0.3/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
+github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
+github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
+github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439 h1:T6zlOdzrYuHf6HUKujm9bzkzbZ5Iv/xf6rs8BHZDpoI=
@@ -65,9 +67,8 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er
 github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
 github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/protobuf v1.5.3-0.20210916003710-5d5e8c018a13 h1:yztvEbaW/qZGubeP7+Lug7PXl7NBfilUK6mw3jq25gQ=
-github.com/golang/protobuf v1.5.3-0.20210916003710-5d5e8c018a13/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -75,19 +76,17 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/gopacket v1.1.19-0.20200831200443-df1bbd09a561 h1:VB5cLlMqQWruyqG6OW/EHDLUawT/hel1I3ElBE4iHg0=
-github.com/google/gopacket v1.1.19-0.20200831200443-df1bbd09a561/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
+github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
+github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
+github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
 github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427 h1:xh96CCAZTX8LJPFoOVRgTwZbn2DvJl8fyCyivohhSIg=
 github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427/go.mod h1:PdjzaU/pJUo4jTIn2rcgMFs+HqBGl/sPJLr8BI0Xq/I=
-github.com/h2non/gock v1.0.9/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE=
-github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47 h1:UnszMmmmm5vLwWzDjTFVIkfhvWF1NdrmChl8L2NUDCw=
-github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
+github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
+github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
 github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -105,14 +104,16 @@ github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
 github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
 github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
 github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
-github.com/keltia/proxy v0.9.3/go.mod h1:fLU4DmBPG0oh0md9fWggE2oG2m7Lchv3eim+GiO3pZY=
-github.com/keltia/ripe-atlas v0.0.0-20211221125000-f6eb808d5dc6/go.mod h1:zYa+dM8811qRhclezc/AKX9imyQwPjjSk2cH0xTgTag=
-github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
-github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
+github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
+github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
+github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
 github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a h1:6SRny9FLB1eWasPyDUqBQnMi9NhXU01XIlB0ao89YoI=
 github.com/marusama/semaphore v0.0.0-20171214154724-565ffd8e868a/go.mod h1:TmeOqAKoDinfPfSohs14CO3VcEf7o+Bem6JiNe05yrQ=
 github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
@@ -136,30 +137,51 @@ github.com/mdlayher/socket v0.0.0-20210624160740-9dbe287ded84 h1:L1jnQ6o+K3M574e
 github.com/mdlayher/socket v0.0.0-20210624160740-9dbe287ded84/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
 github.com/miekg/dns v1.1.44-0.20210804161652-ab67aa642300 h1:cpzamikkKRyu3TZF14CsVFf/CmhlrqZ+7P9aVZYtXz8=
 github.com/miekg/dns v1.1.44-0.20210804161652-ab67aa642300/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
+github.com/mingyech/dtls/v2 v2.0.0 h1:RKN1CjBs8wct3JnrcRmqnfH3BO0ocYLEhmg62uaHq+A=
+github.com/mingyech/dtls/v2 v2.0.0/go.mod h1:Jvjs/Mzb6dWhNbhnobeR2HC8nmjF5AaLezvpVo+PQ1U=
 github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557 h1:w1QuuAA2km2Hax+EPamrq5ZRBeaNv2vsjvgB4an0zoU=
 github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557/go.mod h1:QuAqW7/z+iv6aWFJdrA8kCbsF0OOJVKCICqTcYBexuY=
-github.com/mroth/weightedrand v0.4.1 h1:rHcbUBopmi/3x4nnrvwGJBhX9d0vk+KgoLUZeDP6YyI=
-github.com/mroth/weightedrand v0.4.1/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE=
-github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
-github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
-github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
+github.com/mroth/weightedrand v1.0.0 h1:V8JeHChvl2MP1sAoXq4brElOcza+jxLkRuwvtQu8L3E=
+github.com/mroth/weightedrand v1.0.0/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE=
 github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
 github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
-github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
-github.com/oschwald/maxminddb-golang v1.2.1-0.20170901134056-26fe5ace1c70 h1:2skBNJsk5emoC/p8tOS9DQL5Ia7ND1UNEdmgfvP+mPI=
-github.com/oschwald/maxminddb-golang v1.2.1-0.20170901134056-26fe5ace1c70/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
+github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
+github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc=
+github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
+github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
-github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pebbe/zmq4 v1.2.10 h1:wQkqRZ3CZeABIeidr3e8uQZMMH5YAykA/WN0L5zkd1c=
+github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
+github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
+github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
+github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
+github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
+github.com/pion/sctp v1.8.8 h1:5EdnnKI4gpyR1a1TwbiS/wxEgcUWBHsc7ILAjARJB+U=
+github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs=
+github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
+github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
+github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
+github.com/pion/transport/v2 v2.2.2-0.20230802201558-f2dffd80896b/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
+github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
+github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
 github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
-github.com/refraction-networking/utls v1.0.0/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
+github.com/refraction-networking/conjure v0.7.7 h1:8vWFmSzmkNSrVr1TI4BRbgCD1tc3FTIm40PzjI2olRQ=
+github.com/refraction-networking/conjure v0.7.7/go.mod h1:/UxAcot49ii6ejyvBrSo3g10yyUEavaGJT1Iy47oAfU=
+github.com/refraction-networking/conjure v0.7.8-0.20231019174926-d2f991831312 h1:IP2pvATIC1QBj2/+biLxTJa23PxRIiDwrPvvE4Sh6cw=
+github.com/refraction-networking/conjure v0.7.8-0.20231019174926-d2f991831312/go.mod h1:iOb7GmuSvk/LZsd40L+D/cKmVGIjpFWQkbtOPggJrcA=
+github.com/refraction-networking/ed25519 v0.1.2 h1:08kJZUkAlY7a7cZGosl1teGytV+QEoNxPO7NnRvAB+g=
+github.com/refraction-networking/ed25519 v0.1.2/go.mod h1:nxYLUAYt/hmNpAh64PNSQ/tQ9gTIB89wCaGKJlRtZ9I=
+github.com/refraction-networking/gotapdance v1.7.7 h1:RSdDCA0v4n/iIxCnxLF6uCoJdlo000R+IKGvELfpc/A=
+github.com/refraction-networking/gotapdance v1.7.7/go.mod h1:KORLtX2tKFXb2YDhynsQmGcLmmAHW20CVvdhP5kuAFA=
+github.com/refraction-networking/obfs4 v0.1.2 h1:J842O4fGSkd2W8ogYj0KN6gqVVY+Cpqodw9qFGL7wVU=
+github.com/refraction-networking/obfs4 v0.1.2/go.mod h1:wAl/+gWiLsrcykJA3nKJHx89f5/gXGM8UKvty7+mvbM=
 github.com/refraction-networking/utls v1.3.3 h1:f/TBLX7KBciRyFH3bwupp+CE4fzoYKCirhdRcC490sw=
 github.com/refraction-networking/utls v1.3.3/go.mod h1:DlecWW1LMlMJu+9qpzzQqdHDT/C2LAe03EdpLUz/RL8=
 github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
@@ -167,32 +189,45 @@ github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1T
 github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
 github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 h1:ML7ZNtcln5UBo5Wv7RIv9Xg3Pr5VuRCWLFXEwda54Y4=
 github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507/go.mod h1:DbI1gxrXI2jRGw7XGEUZQOOMd6PsnKzRrCKabvvMrwM=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
-github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 h1:zLV6q4e8Jv9EHjNg/iHfzwDkCve6Ua5jCygptrtXHvI=
 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
-github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 h1:9sreu9e9KOihf2Y0NbpyfWhd1XFDcL4GTkPYL4IvMrg=
 github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78/go.mod h1:HazXTRLhXFyq80TQp7PUXi6BKE6mS+ydEdzEqNBKopQ=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 h1:rzdY78Ox2T+VlXcxGxELF+6VyUXlZBhmRqZu5etLm+c=
+gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0/go.mod h1:70bhd4JKW/+1HLfm+TMrgHJsUHG4coelMWwiVEJ2gAg=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
-golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
+golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
 golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -211,13 +246,20 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
-golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -226,8 +268,6 @@ golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -248,36 +288,61 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
-golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
-golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
+golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
+golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
-golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/grpc v1.58.0 h1:32JY8YpPMSR45K+c3o6b8VL73V+rR8k+DeMIr4vRH8o=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
-google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

+ 162 - 56
psiphon/UDPConn.go

@@ -21,28 +21,60 @@ package psiphon
 
 import (
 	"context"
-	"math/rand"
 	"net"
 	"strconv"
 	"syscall"
 
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
 )
 
-// NewUDPConn resolves addr and configures a new *net.UDPConn. The UDP socket
+// NewUDPConn resolves raddr and configures a new *net.UDPConn. The UDP socket
 // is created using options in DialConfig, including DeviceBinder. The
 // returned UDPAddr uses DialConfig options IPv6Synthesizer and
 // ResolvedIPCallback.
 //
-// The UDP conn is not dialed; it is intended for use with WriteTo using the
-// returned UDPAddr, not Write.
+// The network input may be "udp", "udp4", or "udp6". When "udp4" or "udp6" is
+// specified, the raddr host IP address or resolved domain addresses must
+// include IP address of the corresponding type.
+//
+// If laddr is specified, the UDP socket is bound to that local address. Any
+// laddr host must be an IP address.
+//
+// If useDial is specified, the UDP socket is "connected" to the raddr remote
+// address; otherwise the UDP socket is "unconnected", and each write
+// (WriteTo) can specify an arbitrary remote address.
+//
+// The caller should wrap the returned conn with common.WriteTimeoutUDPConn,
+// as appropriate.
 //
 // The returned conn is not a common.Closer; the caller is expected to wrap
 // this conn with another higher-level conn that provides that interface.
 func NewUDPConn(
-	ctx context.Context, addr string, config *DialConfig) (net.PacketConn, *net.UDPAddr, error) {
+	ctx context.Context,
+	network string,
+	dial bool,
+	laddr string,
+	raddr string,
+	config *DialConfig) (*net.UDPConn, *net.UDPAddr, error) {
+
+	switch network {
+	case "udp", "udp4", "udp6":
+	default:
+		return nil, nil, errors.TraceNew("invalid network")
+	}
+
+	if laddr != "" {
+		localHost, _, err := net.SplitHostPort(laddr)
+		if err != nil {
+			return nil, nil, errors.Trace(err)
+		}
+		if net.ParseIP(localHost) == nil {
+			return nil, nil, errors.TraceNew("invalid local address")
+		}
+	}
 
-	host, strPort, err := net.SplitHostPort(addr)
+	host, strPort, err := net.SplitHostPort(raddr)
 	if err != nil {
 		return nil, nil, errors.Trace(err)
 	}
@@ -68,7 +100,37 @@ func NewUDPConn(
 		return nil, nil, errors.TraceNew("no IP address")
 	}
 
-	ipAddr := ipAddrs[rand.Intn(len(ipAddrs))]
+	var ipAddr net.IP
+
+	if network == "udp" {
+
+		// Pick any IP address, IPv4 or IPv6.
+
+		ipAddr = ipAddrs[prng.Intn(len(ipAddrs))]
+
+		network = "udp4"
+		if ipAddr.To4() == nil {
+			network = "udp6"
+		}
+
+	} else {
+
+		// "udp4" or "udp6" was specified, so pick from either IPv4 or IPv6
+		//  candidates.
+
+		prng.Shuffle(len(ipAddrs), func(i, j int) {
+			ipAddrs[i], ipAddrs[j] = ipAddrs[j], ipAddrs[i]
+		})
+		for _, nextIPAddr := range ipAddrs {
+			if (network == "udp6") == (nextIPAddr.To4() == nil) {
+				ipAddr = nextIPAddr
+				break
+			}
+		}
+		if ipAddr == nil {
+			return nil, nil, errors.TraceNew("no IP address for network")
+		}
+	}
 
 	if config.IPv6Synthesizer != nil {
 		if ipAddr.To4() != nil {
@@ -82,66 +144,110 @@ func NewUDPConn(
 		}
 	}
 
-	listen := &net.ListenConfig{
-		Control: func(_, _ string, c syscall.RawConn) error {
-			var controlErr error
-			err := c.Control(func(fd uintptr) {
+	controlFunc := func(_, _ string, c syscall.RawConn) error {
+		var controlErr error
+		err := c.Control(func(fd uintptr) {
 
-				socketFD := int(fd)
+			socketFD := int(fd)
 
-				setAdditionalSocketOptions(socketFD)
+			setAdditionalSocketOptions(socketFD)
 
-				if config.BPFProgramInstructions != nil {
-					err := setSocketBPF(config.BPFProgramInstructions, socketFD)
-					if err != nil {
-						controlErr = errors.Tracef("setSocketBPF failed: %s", err)
-						return
-					}
+			if config.BPFProgramInstructions != nil {
+				err := setSocketBPF(config.BPFProgramInstructions, socketFD)
+				if err != nil {
+					controlErr = errors.Tracef("setSocketBPF failed: %s", err)
+					return
 				}
+			}
 
-				if config.DeviceBinder != nil {
-					_, err := config.DeviceBinder.BindToDevice(socketFD)
-					if err != nil {
-						controlErr = errors.Tracef("BindToDevice failed: %s", err)
-						return
-					}
+			if config.DeviceBinder != nil {
+				_, err := config.DeviceBinder.BindToDevice(socketFD)
+				if err != nil {
+					controlErr = errors.Tracef("BindToDevice failed: %s", err)
+					return
 				}
-			})
-			if controlErr != nil {
-				return errors.Trace(controlErr)
 			}
-			return errors.Trace(err)
-		},
+		})
+		if controlErr != nil {
+			return errors.Trace(controlErr)
+		}
+		return errors.Trace(err)
 	}
 
-	network := "udp4"
-	if ipAddr.To4() == nil {
-		network = "udp6"
-	}
+	var udpConn *net.UDPConn
 
-	// It's necessary to create an "unconnected" UDP socket, for use with
-	// WriteTo, as required by quic-go. As documented in net.ListenUDP: with
-	// an unspecified IP address, the resulting conn "listens on all
-	// available IP addresses of the local system except multicast IP
-	// addresses".
-	//
-	// Limitation: these UDP sockets are not necessarily closed when a device
-	// changes active network (e.g., WiFi to mobile). It's possible that a
-	// QUIC connection does not immediately close on a network change, and
-	// instead outbound packets are sent from a different active interface.
-	// As quic-go does not yet support connection migration, these packets
-	// will be dropped by the server. This situation is mitigated by use of
-	// DeviceBinder; by network change event detection, which initiates new
-	// tunnel connections; and by timeouts/keep-alives.
-
-	conn, err := listen.ListenPacket(ctx, network, "")
-	if err != nil {
-		return nil, nil, errors.Trace(err)
-	}
+	if dial {
+
+		// Create a "connected" UDP socket, which is associated with a fixed
+		// remote address. Writes will always send to that address.
+		//
+		// This mode is required for the Conjure DTLS transport.
+		//
+		// Unlike non-dial mode, the UDP socket doesn't listen on all local
+		// IPs, and LocalAddr will return an address with a specific IP address.
+
+		var addr net.Addr
+		if laddr != "" {
+
+			// ResolveUDPAddr won't resolve a domain here -- there's a check
+			// above that the host in laddr must be an IP address.
+			addr, err = net.ResolveUDPAddr(network, laddr)
+			if err != nil {
+				return nil, nil, errors.Trace(err)
+			}
+		}
+
+		dialer := &net.Dialer{
+			Control:   controlFunc,
+			LocalAddr: addr,
+		}
 
-	udpConn, ok := conn.(*net.UDPConn)
-	if !ok {
-		return nil, nil, errors.Tracef("unexpected conn type: %T", conn)
+		conn, err := dialer.DialContext(
+			ctx, network, net.JoinHostPort(ipAddr.String(), strPort))
+		if err != nil {
+			return nil, nil, errors.Trace(err)
+		}
+
+		var ok bool
+		udpConn, ok = conn.(*net.UDPConn)
+		if !ok {
+			return nil, nil, errors.Tracef("unexpected conn type: %T", conn)
+		}
+
+	} else {
+
+		// Create an "unconnected" UDP socket, which can be used with WriteTo,
+		// which specifies a remote address per write.
+		//
+		// This mode is required by quic-go.
+		//
+		// As documented in net.ListenUDP: with an unspecified IP address, the
+		// resulting conn "listens on all available IP addresses of the local
+		// system except multicast IP addresses".
+		//
+		// Limitation: these UDP sockets are not necessarily closed when a device
+		// changes active network (e.g., WiFi to mobile). It's possible that a
+		// QUIC connection does not immediately close on a network change, and
+		// instead outbound packets are sent from a different active interface.
+		// As quic-go does not yet support connection migration, these packets
+		// will be dropped by the server. This situation is mitigated by use of
+		// DeviceBinder; by network change event detection, which initiates new
+		// tunnel connections; and by timeouts/keep-alives.
+
+		listen := &net.ListenConfig{
+			Control: controlFunc,
+		}
+
+		conn, err := listen.ListenPacket(ctx, network, laddr)
+		if err != nil {
+			return nil, nil, errors.Trace(err)
+		}
+
+		var ok bool
+		udpConn, ok = conn.(*net.UDPConn)
+		if !ok {
+			return nil, nil, errors.Tracef("unexpected conn type: %T", conn)
+		}
 	}
 
 	if config.ResolvedIPCallback != nil {

+ 77 - 0
psiphon/common/net.go

@@ -26,6 +26,7 @@ import (
 	"net/http"
 	"strconv"
 	"sync"
+	"time"
 
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
@@ -309,3 +310,79 @@ func ParseDNSQuestion(request []byte) (string, error) {
 	}
 	return "", nil
 }
+
+// WriteTimeoutUDPConn sets write deadlines before each UDP packet write.
+//
+// Generally, a UDP packet write doesn't block. However, Go's
+// internal/poll.FD.WriteMsg continues to loop when syscall.SendmsgN fails
+// with EAGAIN, which indicates that an OS socket buffer is currently full;
+// in certain OS states this may cause WriteMsgUDP/etc. to block
+// indefinitely. In this scenario, we want to instead behave as if the packet
+// were dropped, so we set a write deadline which will eventually interrupt
+// any EAGAIN loop.
+type WriteTimeoutUDPConn struct {
+	*net.UDPConn
+}
+
+func (conn *WriteTimeoutUDPConn) Write(b []byte) (int, error) {
+
+	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
+	if err != nil {
+		return 0, errors.Trace(err)
+	}
+
+	// Do not wrap any I/O err returned by udpConn
+	return conn.UDPConn.Write(b)
+}
+
+func (conn *WriteTimeoutUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (int, int, error) {
+
+	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
+	if err != nil {
+		return 0, 0, errors.Trace(err)
+	}
+
+	// Do not wrap any I/O err returned by udpConn
+	return conn.UDPConn.WriteMsgUDP(b, oob, addr)
+}
+
+func (conn *WriteTimeoutUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
+
+	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
+	if err != nil {
+		return 0, errors.Trace(err)
+	}
+
+	// Do not wrap any I/O err returned by udpConn
+	return conn.UDPConn.WriteTo(b, addr)
+}
+
+func (conn *WriteTimeoutUDPConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) {
+
+	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
+	if err != nil {
+		return 0, errors.Trace(err)
+	}
+
+	// Do not wrap any I/O err returned by udpConn
+	return conn.UDPConn.WriteToUDP(b, addr)
+}
+
+// WriteTimeoutPacketConn is the equivilent of WriteTimeoutUDPConn for
+// non-*net.UDPConns.
+type WriteTimeoutPacketConn struct {
+	net.PacketConn
+}
+
+const UDP_PACKET_WRITE_TIMEOUT = 1 * time.Second
+
+func (conn *WriteTimeoutPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
+
+	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
+	if err != nil {
+		return 0, errors.Trace(err)
+	}
+
+	// Do not wrap any I/O err returned by udpConn
+	return conn.PacketConn.WriteTo(b, addr)
+}

+ 23 - 20
psiphon/common/obfuscator/obfuscatedSshConn.go

@@ -546,33 +546,36 @@ func (conn *ObfuscatedSSHConn) transformAndWrite(buffer []byte) error {
 
 		preamble, prefixLen := conn.obfuscator.SendPreamble()
 
-		// Writes the prefix first, then the rest of the preamble after a delay.
-		_, err := conn.Conn.Write(preamble[:prefixLen])
-		if err != nil {
-			return errors.Trace(err)
-		}
+		if prefixLen > 0 {
 
-		// Adds random delay defined by OSSH prefix split config.
-		if config := conn.obfuscator.osshPrefixSplitConfig; config != nil {
-			rng := prng.NewPRNGWithSeed(config.Seed)
-			delay := rng.Period(config.MinDelay, config.MaxDelay)
+			// Writes the prefix first, then the rest of the preamble after a delay.
+			_, err := conn.Conn.Write(preamble[:prefixLen])
+			if err != nil {
+				return errors.Trace(err)
+			}
 
-			timer := time.NewTimer(delay)
+			// Adds random delay defined by OSSH prefix split config.
+			if config := conn.obfuscator.osshPrefixSplitConfig; config != nil {
+				rng := prng.NewPRNGWithSeed(config.Seed)
+				delay := rng.Period(config.MinDelay, config.MaxDelay)
 
-			var err error
-			select {
-			case <-conn.runCtx.Done():
-				err = conn.runCtx.Err()
-			case <-timer.C:
-			}
-			timer.Stop()
+				timer := time.NewTimer(delay)
 
-			if err != nil {
-				return errors.Trace(err)
+				var err error
+				select {
+				case <-conn.runCtx.Done():
+					err = conn.runCtx.Err()
+				case <-timer.C:
+				}
+				timer.Stop()
+
+				if err != nil {
+					return errors.Trace(err)
+				}
 			}
 		}
 
-		_, err = conn.Conn.Write(preamble[prefixLen:])
+		_, err := conn.Conn.Write(preamble[prefixLen:])
 		if err != nil {
 			return errors.Trace(err)
 		}

+ 50 - 3
psiphon/common/parameters/parameters.go

@@ -295,7 +295,12 @@ const (
 	ConjureDecoyRegistrarWidth                       = "ConjureDecoyRegistrarWidth"
 	ConjureDecoyRegistrarMinDelay                    = "ConjureDecoyRegistrarMinDelay"
 	ConjureDecoyRegistrarMaxDelay                    = "ConjureDecoyRegistrarMaxDelay"
-	ConjureTransportObfs4Probability                 = "ConjureTransportObfs4Probability"
+	ConjureEnableIPv6Dials                           = "ConjureEnableIPv6Dials"
+	ConjureEnablePortRandomization                   = "ConjureEnablePortRandomization"
+	ConjureEnableRegistrationOverrides               = "ConjureEnableRegistrationOverrides"
+	ConjureLimitTransportsProbability                = "ConjureLimitTransportsProbability"
+	ConjureLimitTransports                           = "ConjureLimitTransports"
+	ConjureSTUNServerAddresses                       = "ConjureSTUNServerAddresses"
 	CustomHostNameRegexes                            = "CustomHostNameRegexes"
 	CustomHostNameProbability                        = "CustomHostNameProbability"
 	CustomHostNameLimitProtocols                     = "CustomHostNameLimitProtocols"
@@ -674,8 +679,12 @@ var defaultParameters = map[string]struct {
 	ConjureDecoyRegistrarWidth:          {value: 5, minimum: 0},
 	ConjureDecoyRegistrarMinDelay:       {value: time.Duration(0), minimum: time.Duration(0)},
 	ConjureDecoyRegistrarMaxDelay:       {value: time.Duration(0), minimum: time.Duration(0)},
-
-	ConjureTransportObfs4Probability: {value: 0.0, minimum: 0.0},
+	ConjureEnableIPv6Dials:              {value: true},
+	ConjureEnablePortRandomization:      {value: true},
+	ConjureEnableRegistrationOverrides:  {value: false},
+	ConjureLimitTransportsProbability:   {value: 1.0, minimum: 0.0},
+	ConjureLimitTransports:              {value: protocol.ConjureTransports{}},
+	ConjureSTUNServerAddresses:          {value: []string{}},
 
 	CustomHostNameRegexes:        {value: RegexStrings{}},
 	CustomHostNameProbability:    {value: 0.0, minimum: 0.0},
@@ -1168,6 +1177,15 @@ func (p *Parameters) Set(
 					}
 					return nil, errors.Trace(err)
 				}
+			case protocol.ConjureTransports:
+				if skipOnError {
+					newValue = v.PruneInvalid()
+				} else {
+					err := v.Validate()
+					if err != nil {
+						return nil, errors.Trace(err)
+					}
+				}
 			}
 
 			// Enforce any minimums. Assumes defaultParameters[name]
@@ -1686,3 +1704,32 @@ func (p ParametersAccessor) ProtocolTransformScopedSpecNames(name string) transf
 	p.snapshot.getValue(name, &value)
 	return value
 }
+
+// ConjureTransports returns a protocol.ConjureTransports parameter value. If
+// there is a corresponding Probability value, a weighted coin flip will be
+// performed and, depending on the result, the value or the parameter default
+// will be returned.
+func (p ParametersAccessor) ConjureTransports(name string) protocol.ConjureTransports {
+
+	probabilityName := name + "Probability"
+	_, ok := p.snapshot.parameters[probabilityName]
+	if ok {
+		probabilityValue := float64(1.0)
+		p.snapshot.getValue(probabilityName, &probabilityValue)
+		if !prng.FlipWeightedCoin(probabilityValue) {
+			defaultParameter, ok := defaultParameters[name]
+			if ok {
+				defaultValue, ok := defaultParameter.value.(protocol.ConjureTransports)
+				if ok {
+					value := make(protocol.ConjureTransports, len(defaultValue))
+					copy(value, defaultValue)
+					return value
+				}
+			}
+		}
+	}
+
+	value := protocol.ConjureTransports{}
+	p.snapshot.getValue(name, &value)
+	return value
+}

+ 9 - 0
psiphon/common/prng/prng.go

@@ -269,6 +269,11 @@ func (p *PRNG) Perm(n int) []int {
 	return p.rand.Perm(n)
 }
 
+// Shuffle is equivilent to math/rand.Shuffle.
+func (p *PRNG) Shuffle(n int, swap func(i, j int)) {
+	p.rand.Shuffle(n, swap)
+}
+
 // Range selects a random integer in [min, max].
 // If min < 0, min is set to 0. If max < min, min is returned.
 func (p *PRNG) Range(min, max int) int {
@@ -381,6 +386,10 @@ func Perm(n int) []int {
 	return p.Perm(n)
 }
 
+func Shuffle(n int, swap func(i, j int)) {
+	p.Shuffle(n, swap)
+}
+
 func Range(min, max int) int {
 	return p.Range(min, max)
 }

+ 57 - 5
psiphon/common/protocol/protocol.go

@@ -85,9 +85,6 @@ const (
 	CHANNEL_REJECT_REASON_SPLIT_TUNNEL = 0xFE000000
 
 	PSIPHON_API_HANDSHAKE_AUTHORIZATIONS = "authorizations"
-
-	CONJURE_TRANSPORT_MIN_OSSH   = "Min-OSSH"
-	CONJURE_TRANSPORT_OBFS4_OSSH = "Obfs4-OSSH"
 )
 
 var SupportedServerEntrySources = []string{
@@ -108,7 +105,8 @@ type TunnelProtocols []string
 
 func (t TunnelProtocols) Validate() error {
 	for _, p := range t {
-		if !common.Contains(SupportedTunnelProtocols, p) {
+		if !common.Contains(SupportedTunnelProtocols, p) ||
+			common.Contains(DisabledTunnelProtocols, p) {
 			return errors.Tracef("invalid tunnel protocol: %s", p)
 		}
 	}
@@ -118,7 +116,8 @@ func (t TunnelProtocols) Validate() error {
 func (t TunnelProtocols) PruneInvalid() TunnelProtocols {
 	u := make(TunnelProtocols, 0)
 	for _, p := range t {
-		if common.Contains(SupportedTunnelProtocols, p) {
+		if common.Contains(SupportedTunnelProtocols, p) &&
+			!common.Contains(DisabledTunnelProtocols, p) {
 			u = append(u, p)
 		}
 	}
@@ -166,6 +165,22 @@ var DefaultDisabledTunnelProtocols = TunnelProtocols{
 	TUNNEL_PROTOCOL_CONJURE_OBFUSCATED_SSH,
 }
 
+// DisabledTunnelProtocols are protocols which are still integrated, but which
+// cannot be enabled in tactics and cannot be selected by clients.
+var DisabledTunnelProtocols = TunnelProtocols{
+
+	// TUNNEL_PROTOCOL_TAPDANCE_OBFUSCATED_SSH should not be reenabled without
+	// retesting the integration. github.com/refraction-networking/gotapdance
+	// and github.com/refraction-networking/conjure have undergone major
+	// changes since TapDance was last active and tested.
+	//
+	// Furthermore, existing deployed clients will use the same ClientConf for
+	// both TapDance and Conjure, which creates a risk that enabling TapDance
+	// via tactics may cause existing clients to use Conjure ClientConf
+	// decoys for TapDance, which may violate load assumptions.
+	TUNNEL_PROTOCOL_TAPDANCE_OBFUSCATED_SSH,
+}
+
 func TunnelProtocolUsesTCP(protocol string) bool {
 	return protocol != TUNNEL_PROTOCOL_QUIC_OBFUSCATED_SSH &&
 		protocol != TUNNEL_PROTOCOL_FRONTED_MEEK_QUIC_OBFUSCATED_SSH
@@ -524,6 +539,43 @@ func (labeledVersions LabeledQUICVersions) PruneInvalid() LabeledQUICVersions {
 	return l
 }
 
+const (
+	CONJURE_TRANSPORT_MIN_OSSH    = "Min-OSSH"
+	CONJURE_TRANSPORT_PREFIX_OSSH = "Prefix-OSSH"
+	CONJURE_TRANSPORT_DTLS_OSSH   = "DTLS-OSSH"
+)
+
+var SupportedConjureTransports = ConjureTransports{
+	CONJURE_TRANSPORT_MIN_OSSH,
+	CONJURE_TRANSPORT_PREFIX_OSSH,
+	CONJURE_TRANSPORT_DTLS_OSSH,
+}
+
+func ConjureTransportUsesSTUN(transport string) bool {
+	return transport == CONJURE_TRANSPORT_DTLS_OSSH
+}
+
+type ConjureTransports []string
+
+func (transports ConjureTransports) Validate() error {
+	for _, t := range transports {
+		if !common.Contains(SupportedConjureTransports, t) {
+			return errors.Tracef("invalid Conjure transport: %s", t)
+		}
+	}
+	return nil
+}
+
+func (transports ConjureTransports) PruneInvalid() ConjureTransports {
+	u := make(ConjureTransports, 0)
+	for _, t := range transports {
+		if common.Contains(SupportedConjureTransports, t) {
+			u = append(u, t)
+		}
+	}
+	return u
+}
+
 type HandshakeResponse struct {
 	SSHSessionID             string              `json:"ssh_session_id"`
 	Homepages                []string            `json:"homepages"`

+ 4 - 0
psiphon/common/protocol/serverEntry.go

@@ -569,6 +569,10 @@ func (serverEntry *ServerEntry) GetSupportedProtocols(
 			continue
 		}
 
+		if common.Contains(DisabledTunnelProtocols, tunnelProtocol) {
+			continue
+		}
+
 		if len(limitTunnelProtocols) > 0 {
 			if !common.Contains(limitTunnelProtocols, tunnelProtocol) {
 				continue

+ 7 - 83
psiphon/common/quic/quic.go

@@ -69,7 +69,6 @@ const (
 	SERVER_HANDSHAKE_TIMEOUT = 30 * time.Second
 	SERVER_IDLE_TIMEOUT      = 5 * time.Minute
 	CLIENT_IDLE_TIMEOUT      = 30 * time.Second
-	UDP_PACKET_WRITE_TIMEOUT = 1 * time.Second
 )
 
 // Enabled indicates if QUIC functionality is enabled.
@@ -401,8 +400,9 @@ func Dial(
 		// see ObfuscatedPacketConn.writePacket for the server-side
 		// downstream limitation.
 
-		// Ensure blocked packet writes eventually timeout.
-		packetConn = &writeTimeoutPacketConn{
+		// Ensure blocked packet writes eventually timeout. Note that quic-go
+		// manages read deadlines; we set only the write deadline here.
+		packetConn = &common.WriteTimeoutPacketConn{
 			PacketConn: packetConn,
 		}
 
@@ -415,7 +415,7 @@ func Dial(
 	} else {
 
 		// Ensure blocked packet writes eventually timeout.
-		packetConn = &writeTimeoutUDPConn{
+		packetConn = &common.WriteTimeoutUDPConn{
 			UDPConn: udpConn,
 		}
 	}
@@ -527,83 +527,6 @@ func Dial(
 	return conn, nil
 }
 
-// writeTimeoutUDPConn sets write deadlines before each UDP packet write.
-//
-// Generally, a UDP packet write doesn't block. However, Go's
-// internal/poll.FD.WriteMsg continues to loop when syscall.SendmsgN fails
-// with EAGAIN, which indicates that an OS socket buffer is currently full;
-// in certain OS states this may cause WriteMsgUDP/etc. to block
-// indefinitely. In this scenario, we want to instead behave as if the packet
-// were dropped, so we set a write deadline which will eventually interrupt
-// any EAGAIN loop.
-//
-// Note that quic-go manages read deadlines; we set only the write deadline
-// here.
-type writeTimeoutUDPConn struct {
-	*net.UDPConn
-}
-
-func (conn *writeTimeoutUDPConn) Write(b []byte) (int, error) {
-
-	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
-	if err != nil {
-		return 0, errors.Trace(err)
-	}
-
-	// Do not wrap any I/O err returned by udpConn
-	return conn.UDPConn.Write(b)
-}
-
-func (conn *writeTimeoutUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (int, int, error) {
-
-	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
-	if err != nil {
-		return 0, 0, errors.Trace(err)
-	}
-
-	// Do not wrap any I/O err returned by udpConn
-	return conn.UDPConn.WriteMsgUDP(b, oob, addr)
-}
-
-func (conn *writeTimeoutUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
-
-	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
-	if err != nil {
-		return 0, errors.Trace(err)
-	}
-
-	// Do not wrap any I/O err returned by udpConn
-	return conn.UDPConn.WriteTo(b, addr)
-}
-
-func (conn *writeTimeoutUDPConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) {
-
-	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
-	if err != nil {
-		return 0, errors.Trace(err)
-	}
-
-	// Do not wrap any I/O err returned by udpConn
-	return conn.UDPConn.WriteToUDP(b, addr)
-}
-
-// writeTimeoutPacketConn is the equivilent of writeTimeoutUDPConn for
-// non-*net.UDPConns.
-type writeTimeoutPacketConn struct {
-	net.PacketConn
-}
-
-func (conn *writeTimeoutPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
-
-	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
-	if err != nil {
-		return 0, errors.Trace(err)
-	}
-
-	// Do not wrap any I/O err returned by udpConn
-	return conn.PacketConn.WriteTo(b, addr)
-}
-
 // Conn is a net.Conn and psiphon/common.Closer.
 type Conn struct {
 	packetConn net.PacketConn
@@ -884,8 +807,9 @@ func (t *QUICTransporter) dialQUIC() (retConnection quicConnection, retErr error
 		return nil, errors.Tracef("unexpected packetConn type: %T", packetConn)
 	}
 
-	// Ensure blocked packet writes eventually timeout.
-	packetConn = &writeTimeoutUDPConn{
+	// Ensure blocked packet writes eventually timeout. Note that quic-go
+	// manages read deadlines; we set only the write deadline here.
+	packetConn = &common.WriteTimeoutUDPConn{
 		UDPConn: udpConn,
 	}
 

+ 24 - 6
psiphon/common/refraction/config.go

@@ -67,10 +67,10 @@ type ConjureConfig struct {
 	// should be synchronized with the Conjure station configuration.
 	APIRegistrarDelay time.Duration
 
-	// DecoyRegistrarDialer specifies a custom dialer to be used for decoy
-	// registration. Only one of API registration or decoy registration can be
-	// enabled for a single dial.
-	DecoyRegistrarDialer common.NetDialer
+	// DoDecoyRegistration indicates to use decoy registration instead of API
+	// registration. Only one of API registration or decoy registration can
+	// be enabled for a single dial.
+	DoDecoyRegistration bool
 
 	// DecoyRegistrarWidth specifies how many decoys to use per registration.
 	DecoyRegistrarWidth int
@@ -83,10 +83,28 @@ type ConjureConfig struct {
 	// ignored.
 	DecoyRegistrarDelay time.Duration
 
-	// Transport may be protocol.CONJURE_TRANSPORT_MIN_OSSH or
-	// protocol.CONJURE_TRANSPORT_OBFS4_OSSH.
+	// EnableIPv6Dials specifies whether to attempt to dial an IPv6 phantom in
+	// addition to and concurrent with an IPv4 phantom dial.
+	EnableIPv6Dials bool
+
+	// EnablePortRandomization specifies whether to enable destination port
+	// randomization.
+	EnablePortRandomization bool
+
+	// EnableRegistrationOverrides specifies whether to allow the Conjure
+	// system to provide parameter overrides, such as alternative prefixes,
+	// in the registration response.
+	EnableRegistrationOverrides bool
+
+	// Transport may be protocol.CONJURE_TRANSPORT_MIN_OSSH,
+	// protocol.CONJURE_TRANSPORT_PREFIX_OSSH, or
+	// protcol.CONJURE_TRANSPORT_DTLS_OSSH.
 	Transport string
 
+	// STUNServerAddress specifies the STUN server to use with
+	// protcol.CONJURE_TRANSPORT_DTLS_OSSH.
+	STUNServerAddress string
+
 	// DiagnosticID identifies this dial in diagnostics.
 	DiagnosticID string
 

Fișier diff suprimat deoarece este prea mare
+ 2 - 1
psiphon/common/refraction/embedded_config.go


+ 276 - 115
psiphon/common/refraction/refraction.go

@@ -21,16 +21,13 @@
  */
 
 /*
-
 Package refraction wraps github.com/refraction-networking/gotapdance with
 net.Listener and net.Conn types that provide drop-in integration with Psiphon.
-
 */
 package refraction
 
 import (
 	"context"
-	"crypto/sha256"
 	"fmt"
 	"io/ioutil"
 	"net"
@@ -45,7 +42,12 @@ import (
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
 	"github.com/armon/go-proxyproto"
 	lrucache "github.com/cognusion/go-cache-lru"
-	refraction_networking_proto "github.com/refraction-networking/gotapdance/protobuf"
+	refraction_networking_assets "github.com/refraction-networking/conjure/pkg/client/assets"
+	refraction_networking_registration "github.com/refraction-networking/conjure/pkg/registrars/registration"
+	refraction_networking_transports "github.com/refraction-networking/conjure/pkg/transports/client"
+	refraction_networking_dtls "github.com/refraction-networking/conjure/pkg/transports/connecting/dtls"
+	refraction_networking_prefix "github.com/refraction-networking/conjure/pkg/transports/wrapping/prefix"
+	refraction_networking_proto "github.com/refraction-networking/conjure/proto"
 	refraction_networking_client "github.com/refraction-networking/gotapdance/tapdance"
 )
 
@@ -177,6 +179,9 @@ func (c *stationConn) GetMetrics() common.LogFields {
 	return logFields
 }
 
+// Dialer is the dialer function type expected by gotapdance.
+type Dialer func(ctx context.Context, network, laddr, raddr string) (net.Conn, error)
+
 // DialTapDance establishes a new TapDance connection to a TapDance station
 // specified in the config assets and forwarding through to the Psiphon server
 // specified by address.
@@ -195,16 +200,25 @@ func DialTapDance(
 	ctx context.Context,
 	emitLogs bool,
 	dataDirectory string,
-	dialer common.NetDialer,
+	dialer Dialer,
 	address string) (net.Conn, error) {
 
-	return dial(
-		ctx,
-		emitLogs,
-		dataDirectory,
-		dialer,
-		address,
-		nil)
+	// TapDance is disabled. See comment for protocol.DisabledTunnelProtocols.
+	// With that DisabledTunnelProtocols configuration, clients should not
+	// reach this error.
+	//
+	// Note that in addition to this entry point being disabled, the TapDance
+	// ClientConf is no longer initialized in initRefractionNetworking below.
+
+	return nil, errors.TraceNew("not supported")
+
+	// return dial(
+	// 	ctx,
+	//	emitLogs,
+	//	dataDirectory,
+	//	dialer,
+	//	address,
+	//	nil)
 }
 
 // DialConjure establishes a new Conjure connection to a Conjure station.
@@ -217,7 +231,7 @@ func DialConjure(
 	ctx context.Context,
 	emitLogs bool,
 	dataDirectory string,
-	dialer common.NetDialer,
+	dialer Dialer,
 	address string,
 	conjureConfig *ConjureConfig) (net.Conn, error) {
 
@@ -234,7 +248,7 @@ func dial(
 	ctx context.Context,
 	emitLogs bool,
 	dataDirectory string,
-	dialer common.NetDialer,
+	dialer Dialer,
 	address string,
 	conjureConfig *ConjureConfig) (net.Conn, error) {
 
@@ -252,12 +266,16 @@ func dial(
 	manager := newDialManager()
 
 	refractionDialer := &refraction_networking_client.Dialer{
-		TcpDialer:      manager.makeManagedDialer(dialer.DialContext),
-		UseProxyHeader: true,
+		DialerWithLaddr: manager.makeManagedDialer(dialer),
+		V6Support:       conjureConfig.EnableIPv6Dials,
+		UseProxyHeader:  true,
 	}
 
-	conjureCached := false
-	conjureDelay := time.Duration(0)
+	conjureMetricCached := false
+	conjureMetricDelay := time.Duration(0)
+	conjureMetricTransport := ""
+	conjureMetricPrefix := ""
+	conjureMetricSTUNServerAddress := ""
 
 	var conjureCachedRegistration *refraction_networking_client.ConjureReg
 	var conjureRecordRegistrar *recordRegistrar
@@ -317,8 +335,8 @@ func dial(
 				registration: conjureCachedRegistration,
 			}
 
-			conjureCached = true
-			conjureDelay = 0 // report no delay
+			conjureMetricCached = true
+			conjureMetricDelay = 0 // report no delay
 
 		} else if conjureConfig.APIRegistrarBidirectionalURL != "" {
 
@@ -329,27 +347,29 @@ func dial(
 				return nil, errors.TraceNew("missing APIRegistrarHTTPClient")
 			}
 
-			refractionDialer.DarkDecoyRegistrar = &refraction_networking_client.APIRegistrarBidirectional{
-				Endpoint:        conjureConfig.APIRegistrarBidirectionalURL,
-				ConnectionDelay: conjureConfig.APIRegistrarDelay,
-				MaxRetries:      0,
-				Client:          conjureConfig.APIRegistrarHTTPClient,
+			refractionDialer.DarkDecoyRegistrar, err = refraction_networking_registration.NewAPIRegistrar(
+				&refraction_networking_registration.Config{
+					Target:        conjureConfig.APIRegistrarBidirectionalURL,
+					Bidirectional: true,
+					Delay:         conjureConfig.APIRegistrarDelay,
+					MaxRetries:    0,
+					HTTPClient:    conjureConfig.APIRegistrarHTTPClient,
+				})
+			if err != nil {
+				return nil, errors.Trace(err)
 			}
 
-			conjureDelay = conjureConfig.APIRegistrarDelay
+			conjureMetricDelay = conjureConfig.APIRegistrarDelay
 
-		} else if conjureConfig.DecoyRegistrarDialer != nil {
+		} else if conjureConfig.DoDecoyRegistration {
 
-			refractionDialer.DarkDecoyRegistrar = &refraction_networking_client.DecoyRegistrar{
-				TcpDialer: manager.makeManagedDialer(
-					conjureConfig.DecoyRegistrarDialer.DialContext),
-			}
+			refractionDialer.DarkDecoyRegistrar = refraction_networking_registration.NewDecoyRegistrar()
 
 			refractionDialer.Width = conjureConfig.DecoyRegistrarWidth
 
-			// Limitation: the decoy regsitration delay is not currently exposed in the
+			// Limitation: the decoy registration delay is not currently exposed in the
 			// gotapdance API.
-			conjureDelay = -1 // don't report delay
+			conjureMetricDelay = -1 // don't report delay
 
 		} else {
 
@@ -365,22 +385,103 @@ func dial(
 			refractionDialer.DarkDecoyRegistrar = conjureRecordRegistrar
 		}
 
+		// Conjure transport replay limitations:
+		//
+		// - For CONJURE_TRANSPORT_PREFIX_OSSH, the selected prefix is not replayed
+		// - For all transports, randomized port selection is not replayed
+
+		randomizeDstPort := conjureConfig.EnablePortRandomization
+		disableOverrides := !conjureConfig.EnableRegistrationOverrides
+
+		// TODO: EnableRegistrationOverrides is ignored and overrides are
+		// disabled. At this time, overrides appear to undo the
+		// FlushAfterPrefix policy we require for writeMergeConn.
+		disableOverrides = true
+
+		conjureMetricTransport = conjureConfig.Transport
+
 		switch conjureConfig.Transport {
+
 		case protocol.CONJURE_TRANSPORT_MIN_OSSH:
-			refractionDialer.Transport = refraction_networking_proto.TransportType_Min
-			refractionDialer.TcpDialer = newMinTransportDialer(refractionDialer.TcpDialer)
-		case protocol.CONJURE_TRANSPORT_OBFS4_OSSH:
-			refractionDialer.Transport = refraction_networking_proto.TransportType_Obfs4
-		default:
-			return nil, errors.Tracef("invalid Conjure transport: %s", conjureConfig.Transport)
-		}
 
-		if conjureCachedRegistration != nil {
+			transport, ok := refraction_networking_transports.GetTransportByID(
+				refraction_networking_proto.TransportType_Min)
+			if !ok {
+				return nil, errors.TraceNew("missing min transport")
+			}
+
+			config, err := refraction_networking_transports.NewWithParams(
+				transport.Name(),
+				&refraction_networking_proto.GenericTransportParams{
+					RandomizeDstPort: &randomizeDstPort})
+			if err != nil {
+				return nil, errors.Trace(err)
+			}
+
+			refractionDialer.Transport = transport.ID()
+			refractionDialer.TransportConfig = config
+			refractionDialer.DisableRegistrarOverrides = disableOverrides
+			refractionDialer.DialerWithLaddr = newWriteMergeDialer(
+				refractionDialer.DialerWithLaddr, false, 32)
+
+		case protocol.CONJURE_TRANSPORT_PREFIX_OSSH:
+
+			transport, ok := refraction_networking_transports.GetTransportByID(
+				refraction_networking_proto.TransportType_Prefix)
+			if !ok {
+				return nil, errors.TraceNew("missing prefix transport")
+			}
+
+			prefixID := int32(refraction_networking_prefix.Rand)
+			flushPolicy := refraction_networking_prefix.FlushAfterPrefix
+			config, err := refraction_networking_transports.NewWithParams(
+				transport.Name(),
+				&refraction_networking_proto.PrefixTransportParams{
+					RandomizeDstPort:  &randomizeDstPort,
+					PrefixId:          &prefixID,
+					CustomFlushPolicy: &flushPolicy})
+			if err != nil {
+				return nil, errors.Trace(err)
+			}
+
+			refractionDialer.Transport = transport.ID()
+			refractionDialer.TransportConfig = config
+			refractionDialer.DisableRegistrarOverrides = disableOverrides
+			refractionDialer.DialerWithLaddr = newWriteMergeDialer(
+				refractionDialer.DialerWithLaddr, true, 64)
+
+		case protocol.CONJURE_TRANSPORT_DTLS_OSSH:
+
+			transport, ok := refraction_networking_transports.GetTransportByID(
+				refraction_networking_proto.TransportType_DTLS)
+			if !ok {
+				return nil, errors.TraceNew("missing DTLS transport")
+			}
+
+			config, err := refraction_networking_transports.NewWithParams(
+				transport.Name(),
+				&refraction_networking_proto.DTLSTransportParams{
+					RandomizeDstPort: &randomizeDstPort})
+			if err != nil {
+				return nil, errors.Trace(err)
+			}
+
+			if conjureConfig.STUNServerAddress == "" {
+				return nil, errors.TraceNew("missing STUN server address")
+			}
+			config.SetParams(
+				&refraction_networking_dtls.ClientConfig{
+					STUNServer: conjureConfig.STUNServerAddress,
+				})
+
+			conjureMetricSTUNServerAddress = conjureConfig.STUNServerAddress
 
-			// When using a cached registration, patch its TcpDialer to use the custom
-			// dialer for this dial. In the non-cached code path, gotapdance will set
-			// refractionDialer.TcpDialer into a new registration.
-			conjureCachedRegistration.TcpDialer = refractionDialer.TcpDialer
+			refractionDialer.Transport = transport.ID()
+			refractionDialer.TransportConfig = config
+			refractionDialer.DisableRegistrarOverrides = disableOverrides
+
+		default:
+			return nil, errors.Tracef("invalid Conjure transport: %s", conjureConfig.Transport)
 		}
 	}
 
@@ -436,14 +537,14 @@ func dial(
 			// conjureRecordRegistrar.registration will be nil if there was no cached
 			// registration _and_ registration didn't succeed before a cancel.
 			if registration != nil {
+				conjureRegistrationCache.put(conjureConfig, registration, isCanceled)
 
-				// Do not retain a reference to the custom dialer, as its context will not
-				// be valid for future dials using this cached registration. Assumes that
-				// gotapdance will no longer reference the TcpDialer now that the
-				// connection is established.
-				registration.TcpDialer = nil
+				if conjureConfig.Transport == protocol.CONJURE_TRANSPORT_PREFIX_OSSH {
 
-				conjureRegistrationCache.put(conjureConfig, registration, isCanceled)
+					// Record the selected prefix name after registration, as
+					// the registrar may have overridden the client selection.
+					conjureMetricPrefix = registration.Transport.Name()
+				}
 			}
 
 		} else if conjureCachedRegistration != nil {
@@ -471,9 +572,11 @@ func dial(
 	if useConjure {
 		// Retain these values for logging metrics.
 		refractionConn.isConjure = true
-		refractionConn.conjureCached = conjureCached
-		refractionConn.conjureDelay = conjureDelay
-		refractionConn.conjureTransport = conjureConfig.Transport
+		refractionConn.conjureMetricCached = conjureMetricCached
+		refractionConn.conjureMetricDelay = conjureMetricDelay
+		refractionConn.conjureMetricTransport = conjureMetricTransport
+		refractionConn.conjureMetricPrefix = conjureMetricPrefix
+		refractionConn.conjureMetricSTUNServerAddress = conjureMetricSTUNServerAddress
 	}
 
 	return refractionConn, nil
@@ -617,6 +720,10 @@ func (r *cachedRegistrar) Register(
 	return r.registration, nil
 }
 
+func (r *cachedRegistrar) PrepareRegKeys(_ [32]byte, _ []byte) error {
+	return nil
+}
+
 type recordRegistrar struct {
 	registrar    refraction_networking_client.Registrar
 	registration *refraction_networking_client.ConjureReg
@@ -634,14 +741,28 @@ func (r *recordRegistrar) Register(
 	return registration, nil
 }
 
-// minTransportConn buffers the first 32-byte random HMAC write performed by
-// Conjure TransportType_Min, and prepends it to the subsequent first write
-// made by OSSH. The purpose is to avoid a distinct fingerprint consisting of
-// the initial TCP data packet always containing exactly 32 bytes of payload.
-// The first write by OSSH will be a variable length multi-packet-sized
-// sequence of random bytes.
-type minTransportConn struct {
+func (r *recordRegistrar) PrepareRegKeys(_ [32]byte, _ []byte) error {
+	return nil
+}
+
+// writeMergeConn merges Conjure transport and subsequent OSSH writes in order
+// to avoid fixed-sized first or second TCP packets always containing exactly
+// the 32-byte or 64-byte HMAC tag.
+//
+// The Conjure Prefix transport will first write a prefix. writeMergeConn
+// assumes the FlushAfterPrefix policy is used, so the first write call for
+// that transport will be exactly the arbitrary sized prefix. The second
+// write call will be the HMAC tag. Pass the first write through to the
+// underlying conn, and then expect the HMAC tag on the second write, and
+// handle as follows.
+//
+// The Conjure Min transport first calls write with an HMAC tag. Buffer this
+// value and await the following initial OSSH write, and prepend the buffered
+// HMAC tag to the random OSSH data. The first write by OSSH will be a
+// variable length multi-packet-sized sequence of random bytes.
+type writeMergeConn struct {
 	net.Conn
+	tagSize int
 
 	mutex  sync.Mutex
 	state  int
@@ -650,67 +771,83 @@ type minTransportConn struct {
 }
 
 const (
-	stateMinTransportInit = iota
-	stateMinTransportBufferedHMAC
-	stateMinTransportWroteHMAC
-	stateMinTransportFailed
+	stateWriteMergeAwaitingPrefix = iota
+	stateWriteMergeAwaitingTag
+	stateWriteMergeBufferedTag
+	stateWriteMergeFinishedMerging
+	stateWriteMergeFailed
 )
 
-func newMinTransportConn(conn net.Conn) *minTransportConn {
-	return &minTransportConn{
-		Conn:  conn,
-		state: stateMinTransportInit,
+func newWriteMergeConn(conn net.Conn, hasPrefix bool, tagSize int) *writeMergeConn {
+	c := &writeMergeConn{
+		Conn:    conn,
+		tagSize: tagSize,
+	}
+	if hasPrefix {
+		c.state = stateWriteMergeAwaitingPrefix
+	} else {
+		c.state = stateWriteMergeAwaitingTag
 	}
+	return c
 }
 
-func (conn *minTransportConn) Write(p []byte) (int, error) {
+func (conn *writeMergeConn) Write(p []byte) (int, error) {
 	conn.mutex.Lock()
 	defer conn.mutex.Unlock()
 
 	switch conn.state {
-	case stateMinTransportInit:
-		if len(p) != sha256.Size {
-			conn.state = stateMinTransportFailed
-			conn.err = errors.TraceNew("unexpected HMAC write size")
+
+	case stateWriteMergeAwaitingPrefix:
+		conn.state = stateWriteMergeAwaitingTag
+		return conn.Conn.Write(p)
+
+	case stateWriteMergeAwaitingTag:
+		if len(p) != conn.tagSize {
+			conn.state = stateWriteMergeFailed
+			conn.err = errors.Tracef("unexpected tag write size: %d", len(p))
 			return 0, conn.err
 		}
-		conn.buffer = make([]byte, sha256.Size)
+		conn.buffer = make([]byte, conn.tagSize)
 		copy(conn.buffer, p)
-		conn.state = stateMinTransportBufferedHMAC
-		return sha256.Size, nil
-	case stateMinTransportBufferedHMAC:
+		conn.state = stateWriteMergeBufferedTag
+		return conn.tagSize, nil
+
+	case stateWriteMergeBufferedTag:
 		conn.buffer = append(conn.buffer, p...)
 		n, err := conn.Conn.Write(conn.buffer)
-		if n < sha256.Size {
-			conn.state = stateMinTransportFailed
-			conn.err = errors.TraceNew("failed to write HMAC")
-			if err == nil {
-				// As Write must return an error when failing to write the entire buffer,
-				// we don't expect to hit this case.
-				err = conn.err
-			}
+		if err != nil {
+			conn.state = stateWriteMergeFailed
+			conn.err = errors.Trace(err)
 		} else {
-			conn.state = stateMinTransportWroteHMAC
+			conn.state = stateWriteMergeFinishedMerging
+			conn.buffer = nil
+		}
+		n -= conn.tagSize
+		if n < 0 {
+			n = 0
 		}
-		n -= sha256.Size
-		// Do not wrap Conn.Write errors, and do not return conn.err here.
+		// Do not wrap Conn.Write errors
 		return n, err
-	case stateMinTransportWroteHMAC:
+
+	case stateWriteMergeFinishedMerging:
 		return conn.Conn.Write(p)
-	case stateMinTransportFailed:
+
+	case stateWriteMergeFailed:
+		// Return the original error that caused the failure
 		return 0, conn.err
+
 	default:
 		return 0, errors.TraceNew("unexpected state")
 	}
 }
 
-func newMinTransportDialer(dialer common.Dialer) common.Dialer {
-	return func(ctx context.Context, network, address string) (net.Conn, error) {
-		conn, err := dialer(ctx, network, address)
+func newWriteMergeDialer(dialer Dialer, hasPrefix bool, tagSize int) Dialer {
+	return func(ctx context.Context, network, laddr, raddr string) (net.Conn, error) {
+		conn, err := dialer(ctx, network, laddr, raddr)
 		if err != nil {
 			return nil, errors.Trace(err)
 		}
-		return newMinTransportConn(conn), nil
+		return newWriteMergeConn(conn, hasPrefix, tagSize), nil
 	}
 }
 
@@ -721,6 +858,9 @@ func newMinTransportDialer(dialer common.Dialer) common.Dialer {
 // are interrupted:
 // E.g., https://github.com/refraction-networking/gotapdance/blob/4d84655dad2e242b0af0459c31f687b12085dcca/tapdance/conn_raw.go#L307
 // (...preceeding SetDeadline is insufficient for immediate cancellation.)
+//
+// This remains an issue with the Conjure Decoy Registrar:
+// https://github.com/refraction-networking/conjure/blob/d9d58260cc7017ab0c64b120579b123a5b2d1c96/pkg/registrars/decoy-registrar/decoy-registrar.go#L208
 type dialManager struct {
 	ctxMutex       sync.Mutex
 	useRunCtx      bool
@@ -740,22 +880,19 @@ func newDialManager() *dialManager {
 	}
 }
 
-func (manager *dialManager) makeManagedDialer(dialer common.Dialer) common.Dialer {
+func (manager *dialManager) makeManagedDialer(dialer Dialer) Dialer {
 
-	return func(ctx context.Context, network, address string) (net.Conn, error) {
-		return manager.dialWithDialer(dialer, ctx, network, address)
+	return func(ctx context.Context, network, laddr, raddr string) (net.Conn, error) {
+		return manager.dialWithDialer(dialer, ctx, network, laddr, raddr)
 	}
 }
 
 func (manager *dialManager) dialWithDialer(
-	dialer common.Dialer,
+	dialer Dialer,
 	ctx context.Context,
 	network string,
-	address string) (net.Conn, error) {
-
-	if network != "tcp" {
-		return nil, errors.Tracef("unsupported network: %s", network)
-	}
+	laddr string,
+	raddr string) (net.Conn, error) {
 
 	// The context for this dial is either:
 	// - ctx, during the initial refraction_networking_client.DialContext, when
@@ -779,7 +916,7 @@ func (manager *dialManager) dialWithDialer(
 	}
 	manager.ctxMutex.Unlock()
 
-	conn, err := dialer(ctx, network, address)
+	conn, err := dialer(ctx, network, laddr, raddr)
 	if err != nil {
 		return nil, errors.Trace(err)
 	}
@@ -789,7 +926,7 @@ func (manager *dialManager) dialWithDialer(
 	// TapDance will run in a degraded state.
 	// Limitation: if the underlying conn _also_ passes through CloseWrite, this
 	// check may be insufficient.
-	if _, ok := conn.(common.CloseWriter); !ok {
+	if _, ok := conn.(common.CloseWriter); network == "tcp" && !ok {
 		return nil, errors.TraceNew("underlying conn is not a CloseWriter")
 	}
 
@@ -844,10 +981,12 @@ type refractionConn struct {
 	manager  *dialManager
 	isClosed int32
 
-	isConjure        bool
-	conjureCached    bool
-	conjureDelay     time.Duration
-	conjureTransport string
+	isConjure                      bool
+	conjureMetricCached            bool
+	conjureMetricDelay             time.Duration
+	conjureMetricTransport         string
+	conjureMetricPrefix            string
+	conjureMetricSTUNServerAddress string
 }
 
 func (conn *refractionConn) Close() error {
@@ -867,17 +1006,33 @@ func (conn *refractionConn) GetMetrics() common.LogFields {
 	if conn.isConjure {
 
 		cached := "0"
-		if conn.conjureCached {
+		if conn.conjureMetricCached {
 			cached = "1"
 		}
 		logFields["conjure_cached"] = cached
 
-		if conn.conjureDelay != -1 {
-			logFields["conjure_delay"] = fmt.Sprintf("%d", conn.conjureDelay/time.Millisecond)
+		if conn.conjureMetricDelay != -1 {
+			logFields["conjure_delay"] = fmt.Sprintf("%d", conn.conjureMetricDelay/time.Millisecond)
 		}
 
-		logFields["conjure_transport"] = conn.conjureTransport
+		logFields["conjure_transport"] = conn.conjureMetricTransport
+
+		if conn.conjureMetricPrefix != "" {
+			logFields["conjure_prefix"] = conn.conjureMetricPrefix
+		}
+
+		if conn.conjureMetricSTUNServerAddress != "" {
+			logFields["conjure_stun"] = conn.conjureMetricSTUNServerAddress
+		}
+
+		logFields["conjure_network"] = conn.RemoteAddr().Network()
+
+		_, port, err := net.SplitHostPort(conn.RemoteAddr().String())
+		if err == nil {
+			logFields["conjure_port_number"] = port
+		}
 	}
+
 	return logFields
 }
 
@@ -910,7 +1065,13 @@ func initRefractionNetworking(emitLogs bool, dataDirectory string) error {
 			return
 		}
 
-		refraction_networking_client.AssetsSetDir(assetsDir)
+		refraction_networking_assets.AssetsSetDir(assetsDir)
+
+		// TapDance now uses a distinct Assets/ClientConf,
+		// refraction_networking_client.Assets. Do not configure the TapDance
+		// ClientConf to use the same configuration as Conjure, as the
+		// Conjure ClientConf may contain decoys that are appropriate for
+		// registration load but not full TapDance tunnel load.
 	})
 
 	return initErr

+ 12 - 9
psiphon/common/resolver/resolver.go

@@ -175,6 +175,10 @@ type ResolveParameters struct {
 	// of making a request.
 	PreresolvedIPAddress string
 
+	// PreresolvedDomain is the domain for which PreresolvedIPAddress is to be
+	// used.
+	PreresolvedDomain string
+
 	// AlternateDNSServer specifies an alterate DNS server (IP:port, or IP
 	// only with port 53 assumed) to be used when either no system DNS
 	// servers are available or when PreferAlternateDNSServer is set.
@@ -314,7 +318,8 @@ func (r *Resolver) Stop() {
 // parameters and optional frontingProviderID context.
 func (r *Resolver) MakeResolveParameters(
 	p parameters.ParametersAccessor,
-	frontingProviderID string) (*ResolveParameters, error) {
+	frontingProviderID string,
+	frontingDialDomain string) (*ResolveParameters, error) {
 
 	params := &ResolveParameters{
 		AttemptsPerServer:          p.Int(parameters.DNSResolverAttemptsPerServer),
@@ -326,6 +331,9 @@ func (r *Resolver) MakeResolveParameters(
 	// When a frontingProviderID is specified, generate a pre-resolved IP
 	// address, based on tactics configuration.
 	if frontingProviderID != "" {
+		if frontingDialDomain == "" {
+			return nil, errors.TraceNew("missing fronting dial domain")
+		}
 		if p.WeightedCoinFlip(parameters.DNSResolverPreresolvedIPAddressProbability) {
 			CIDRs := p.LabeledCIDRs(parameters.DNSResolverPreresolvedIPAddressCIDRs, frontingProviderID)
 			if len(CIDRs) > 0 {
@@ -335,16 +343,11 @@ func (r *Resolver) MakeResolveParameters(
 					return nil, errors.Trace(err)
 				}
 				params.PreresolvedIPAddress = IP.String()
+				params.PreresolvedDomain = frontingDialDomain
 			}
 		}
 	}
 
-	// When PreresolvedIPAddress is set, there's no DNS request and the
-	// following params can be skipped.
-	if params.PreresolvedIPAddress != "" {
-		return params, nil
-	}
-
 	// When preferring an alternate DNS server, select the alternate from
 	// DNSResolverPreferredAlternateServers. This list is for circumvention
 	// operations, such as using a public DNS server with a protocol
@@ -496,7 +499,7 @@ func (r *Resolver) ResolveIP(
 	ctx context.Context,
 	networkID string,
 	params *ResolveParameters,
-	hostname string) ([]net.IP, error) {
+	hostname string) (x []net.IP, y error) {
 
 	// ResolveIP does _not_ lock r.mutex for the lifetime of the function, to
 	// ensure many ResolveIP calls can run concurrently.
@@ -536,7 +539,7 @@ func (r *Resolver) ResolveIP(
 
 	// When PreresolvedIPAddress is set, tactics parameters determined the IP address
 	// in this case.
-	if params.PreresolvedIPAddress != "" {
+	if params.PreresolvedIPAddress != "" && params.PreresolvedDomain == hostname {
 		IP := net.ParseIP(params.PreresolvedIPAddress)
 		if IP == nil {
 			// Unexpected case, as MakeResolveParameters selects the IP address.

+ 35 - 8
psiphon/common/resolver/resolver_test.go

@@ -62,6 +62,7 @@ func TestPublicDNSServers(t *testing.T) {
 func runTestMakeResolveParameters() error {
 
 	frontingProviderID := "frontingProvider"
+	frontingDialDomain := exampleDomain
 	alternateDNSServer := "172.16.0.1"
 	alternateDNSServerWithPort := net.JoinHostPort(alternateDNSServer, resolverDNSPort)
 	preferredAlternateDNSServer := "172.16.0.2"
@@ -95,7 +96,7 @@ func runTestMakeResolveParameters() error {
 	defer resolver.Stop()
 
 	resolverParams, err := resolver.MakeResolveParameters(
-		params.Get(), frontingProviderID)
+		params.Get(), frontingProviderID, frontingDialDomain)
 	if err != nil {
 		return errors.Trace(err)
 	}
@@ -112,11 +113,7 @@ func runTestMakeResolveParameters() error {
 		resolverParams.RequestTimeout != 5*time.Second ||
 		resolverParams.AwaitTimeout != 10*time.Millisecond ||
 		!CIDRContainsIP(exampleIPv4CIDR, resolverParams.PreresolvedIPAddress) ||
-		resolverParams.AlternateDNSServer != "" ||
-		resolverParams.PreferAlternateDNSServer != false ||
-		resolverParams.ProtocolTransformName != "" ||
-		resolverParams.ProtocolTransformSpec != nil ||
-		resolverParams.IncludeEDNS0 != false {
+		resolverParams.PreresolvedDomain != frontingDialDomain {
 		return errors.Tracef("unexpected resolver parameters: %+v", resolverParams)
 	}
 
@@ -145,7 +142,7 @@ func runTestMakeResolveParameters() error {
 	}
 
 	resolverParams, err = resolver.MakeResolveParameters(
-		params.Get(), frontingProviderID)
+		params.Get(), frontingProviderID, frontingDialDomain)
 	if err != nil {
 		return errors.Trace(err)
 	}
@@ -155,6 +152,7 @@ func runTestMakeResolveParameters() error {
 		resolverParams.RequestTimeout != 5*time.Second ||
 		resolverParams.AwaitTimeout != 10*time.Millisecond ||
 		resolverParams.PreresolvedIPAddress != "" ||
+		resolverParams.PreresolvedDomain != "" ||
 		resolverParams.AlternateDNSServer != preferredAlternateDNSServerWithPort ||
 		resolverParams.PreferAlternateDNSServer != true ||
 		resolverParams.ProtocolTransformName != transformName ||
@@ -175,7 +173,7 @@ func runTestMakeResolveParameters() error {
 	}
 
 	resolverParams, err = resolver.MakeResolveParameters(
-		params.Get(), frontingProviderID)
+		params.Get(), frontingProviderID, frontingDialDomain)
 	if err != nil {
 		return errors.Trace(err)
 	}
@@ -185,6 +183,7 @@ func runTestMakeResolveParameters() error {
 		resolverParams.RequestTimeout != 5*time.Second ||
 		resolverParams.AwaitTimeout != 10*time.Millisecond ||
 		resolverParams.PreresolvedIPAddress != "" ||
+		resolverParams.PreresolvedDomain != "" ||
 		resolverParams.AlternateDNSServer != alternateDNSServerWithPort ||
 		resolverParams.PreferAlternateDNSServer != false ||
 		resolverParams.ProtocolTransformName != "" ||
@@ -331,6 +330,7 @@ func runTestResolver() error {
 	beforeMetrics = resolver.metrics
 
 	params.PreresolvedIPAddress = exampleIPv4
+	params.PreresolvedDomain = exampleDomain
 
 	IPs, err = resolver.ResolveIP(ctx, networkID, params, exampleDomain)
 	if err != nil {
@@ -350,6 +350,33 @@ func runTestResolver() error {
 
 	params.PreresolvedIPAddress = ""
 
+	// Test: PreresolvedIPAddress set for different domain
+
+	beforeMetrics = resolver.metrics
+
+	params.PreresolvedIPAddress = exampleIPv4
+	params.PreresolvedDomain = "not.example.com"
+
+	IPs, err = resolver.ResolveIP(ctx, networkID, params, exampleDomain)
+	if err != nil {
+		return errors.Trace(err)
+	}
+
+	err = checkResult(IPs)
+	if err != nil {
+		return errors.Trace(err)
+	}
+
+	if resolver.metrics.resolves != beforeMetrics.resolves+1 ||
+		resolver.metrics.cacheHits != beforeMetrics.cacheHits+1 ||
+		resolver.metrics.requestsIPv4 != beforeMetrics.requestsIPv4 ||
+		resolver.metrics.requestsIPv6 != beforeMetrics.requestsIPv6 {
+		return errors.Tracef("unexpected metrics: %+v", resolver.metrics)
+	}
+
+	params.PreresolvedIPAddress = ""
+	params.PreresolvedDomain = ""
+
 	// Test: change network ID, which must clear cache
 
 	beforeMetrics = resolver.metrics

+ 39 - 0
psiphon/config.go

@@ -797,6 +797,11 @@ type Config struct {
 	ConjureDecoyRegistrarWidth                *int
 	ConjureDecoyRegistrarMinDelayMilliseconds *int
 	ConjureDecoyRegistrarMaxDelayMilliseconds *int
+	ConjureEnableIPv6Dials                    *bool
+	ConjureEnablePortRandomization            *bool
+	ConjureEnableRegistrationOverrides        *bool
+	ConjureLimitTransports                    protocol.ConjureTransports
+	ConjureSTUNServerAddresses                []string
 
 	// HoldOffTunnelMinDurationMilliseconds and other HoldOffTunnel fields are
 	// for testing purposes.
@@ -1867,6 +1872,26 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
 		applyParameters[parameters.ConjureDecoyRegistrarMaxDelay] = fmt.Sprintf("%dms", *config.ConjureDecoyRegistrarMaxDelayMilliseconds)
 	}
 
+	if config.ConjureEnableIPv6Dials != nil {
+		applyParameters[parameters.ConjureEnableIPv6Dials] = *config.ConjureEnableIPv6Dials
+	}
+
+	if config.ConjureEnablePortRandomization != nil {
+		applyParameters[parameters.ConjureEnablePortRandomization] = *config.ConjureEnablePortRandomization
+	}
+
+	if config.ConjureEnableRegistrationOverrides != nil {
+		applyParameters[parameters.ConjureEnableRegistrationOverrides] = *config.ConjureEnableRegistrationOverrides
+	}
+
+	if config.ConjureLimitTransports != nil {
+		applyParameters[parameters.ConjureLimitTransports] = config.ConjureLimitTransports
+	}
+
+	if config.ConjureSTUNServerAddresses != nil {
+		applyParameters[parameters.ConjureSTUNServerAddresses] = config.ConjureSTUNServerAddresses
+	}
+
 	if config.HoldOffTunnelMinDurationMilliseconds != nil {
 		applyParameters[parameters.HoldOffTunnelMinDuration] = fmt.Sprintf("%dms", *config.HoldOffTunnelMinDurationMilliseconds)
 	}
@@ -2328,6 +2353,20 @@ func (config *Config) setDialParametersHash() {
 		binary.Write(hash, binary.LittleEndian, int64(*config.ConjureDecoyRegistrarMaxDelayMilliseconds))
 	}
 
+	if config.ConjureLimitTransports != nil {
+		hash.Write([]byte("ConjureLimitTransports"))
+		for _, transport := range config.ConjureLimitTransports {
+			hash.Write([]byte(transport))
+		}
+	}
+
+	if config.ConjureSTUNServerAddresses != nil {
+		hash.Write([]byte("ConjureSTUNServerAddresses"))
+		for _, address := range config.ConjureSTUNServerAddresses {
+			hash.Write([]byte(address))
+		}
+	}
+
 	if config.HoldOffTunnelMinDurationMilliseconds != nil {
 		hash.Write([]byte("HoldOffTunnelMinDurationMilliseconds"))
 		binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffTunnelMinDurationMilliseconds))

+ 0 - 1
psiphon/dataStore.go

@@ -609,7 +609,6 @@ type ServerEntryIterator struct {
 // NewServerEntryIterator and any returned ServerEntryIterator are not
 // designed for concurrent use as not all related datastore operations are
 // performed in a single transaction.
-//
 func NewServerEntryIterator(config *Config) (bool, *ServerEntryIterator, error) {
 
 	// When configured, this target server entry is the only candidate

+ 40 - 5
psiphon/dialParameters.go

@@ -138,6 +138,7 @@ type DialParameters struct {
 	ConjureDecoyRegistrarDelay          time.Duration
 	ConjureDecoyRegistrarWidth          int
 	ConjureTransport                    string
+	ConjureSTUNServerAddress            string
 
 	LivenessTestSeed *prng.Seed
 
@@ -623,10 +624,18 @@ func MakeDialParameters(
 	if (!isReplay || !replayConjureTransport) &&
 		protocol.TunnelProtocolUsesConjure(dialParams.TunnelProtocol) {
 
-		dialParams.ConjureTransport = protocol.CONJURE_TRANSPORT_MIN_OSSH
-		if p.WeightedCoinFlip(
-			parameters.ConjureTransportObfs4Probability) {
-			dialParams.ConjureTransport = protocol.CONJURE_TRANSPORT_OBFS4_OSSH
+		// None of ConjureEnableIPv6Dials, ConjureEnablePortRandomization, or
+		// ConjureEnableRegistrationOverrides are set here for replay. The
+		// current value of these flag parameters is always applied.
+
+		dialParams.ConjureTransport = selectConjureTransport(p)
+		if protocol.ConjureTransportUsesSTUN(dialParams.ConjureTransport) {
+			stunServerAddresses := p.Strings(parameters.ConjureSTUNServerAddresses)
+			if len(stunServerAddresses) == 0 {
+				return nil, errors.Tracef(
+					"no Conjure STUN servers addresses configured for transport %s", dialParams.ConjureTransport)
+			}
+			dialParams.ConjureSTUNServerAddress = stunServerAddresses[prng.Intn(len(stunServerAddresses))]
 		}
 	}
 
@@ -824,7 +833,7 @@ func MakeDialParameters(
 	if (!isReplay || !replayResolveParameters) && useResolver {
 
 		dialParams.ResolveParameters, err = dialParams.resolver.MakeResolveParameters(
-			p, dialParams.FrontingProviderID)
+			p, dialParams.FrontingProviderID, dialParams.MeekFrontingDialAddress)
 		if err != nil {
 			return nil, errors.Trace(err)
 		}
@@ -1720,3 +1729,29 @@ func makeOSSHPrefixSplitConfig(p parameters.ParametersAccessor) (*obfuscator.OSS
 		MaxDelay: maxDelay,
 	}, nil
 }
+
+func selectConjureTransport(
+	p parameters.ParametersAccessor) string {
+
+	limitConjureTransports := p.ConjureTransports(parameters.ConjureLimitTransports)
+
+	transports := make([]string, 0)
+
+	for _, transport := range protocol.SupportedConjureTransports {
+
+		if len(limitConjureTransports) > 0 &&
+			!common.Contains(limitConjureTransports, transport) {
+			continue
+		}
+
+		transports = append(transports, transport)
+	}
+
+	if len(transports) == 0 {
+		return ""
+	}
+
+	choice := prng.Intn(len(transports))
+
+	return transports[choice]
+}

+ 1 - 3
psiphon/meekConn.go

@@ -381,9 +381,7 @@ func DialMeek(
 
 		udpDialer := func(ctx context.Context) (net.PacketConn, *net.UDPAddr, error) {
 			packetConn, remoteAddr, err := NewUDPConn(
-				ctx,
-				meekConfig.DialAddress,
-				dialConfig)
+				ctx, "udp", false, "", meekConfig.DialAddress, dialConfig)
 			if err != nil {
 				return nil, nil, errors.Trace(err)
 			}

+ 34 - 20
psiphon/net.go

@@ -188,35 +188,49 @@ type NetworkIDGetter interface {
 	GetNetworkID() string
 }
 
-// NetDialer implements an interface that matches net.Dialer.
-// Limitation: only "tcp" Dials are supported.
-type NetDialer struct {
-	dialTCP common.Dialer
-}
+// RefractionNetworkingDialer implements psiphon/common/refraction.Dialer.
 
-// NewNetDialer creates a new NetDialer.
-func NewNetDialer(config *DialConfig) *NetDialer {
-	return &NetDialer{
-		dialTCP: NewTCPDialer(config),
-	}
+type RefractionNetworkingDialer struct {
+	config *DialConfig
 }
 
-func (d *NetDialer) Dial(network, address string) (net.Conn, error) {
-	conn, err := d.DialContext(context.Background(), network, address)
-	if err != nil {
-		return nil, errors.Trace(err)
+// NewRefractionNetworkingDialer creates a new RefractionNetworkingDialer.
+func NewRefractionNetworkingDialer(config *DialConfig) *RefractionNetworkingDialer {
+	return &RefractionNetworkingDialer{
+		config: config,
 	}
-	return conn, nil
 }
 
-func (d *NetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
+func (d *RefractionNetworkingDialer) DialContext(
+	ctx context.Context,
+	network string,
+	laddr string,
+	raddr string) (net.Conn, error) {
+
 	switch network {
-	case "tcp":
-		conn, err := d.dialTCP(ctx, "tcp", address)
+	case "tcp", "tcp4", "tcp6":
+
+		if laddr != "" {
+			return nil, errors.TraceNew("unexpected laddr for tcp dial")
+		}
+		conn, err := DialTCP(ctx, raddr, d.config)
 		if err != nil {
 			return nil, errors.Trace(err)
 		}
 		return conn, nil
+
+	case "udp", "udp4", "udp6":
+
+		udpConn, _, err := NewUDPConn(ctx, network, true, laddr, raddr, d.config)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		// Ensure blocked packet writes eventually timeout.
+		conn := &common.WriteTimeoutUDPConn{
+			UDPConn: udpConn,
+		}
+		return conn, nil
+
 	default:
 		return nil, errors.Tracef("unsupported network: %s", network)
 	}
@@ -367,14 +381,14 @@ func UntunneledResolveIP(
 	ctx context.Context,
 	config *Config,
 	resolver *resolver.Resolver,
-	hostname,
+	hostname string,
 	frontingProviderID string) ([]net.IP, error) {
 
 	// Limitations: for untunneled resolves, there is currently no resolve
 	// parameter replay, and no support for pre-resolved IPs.
 
 	params, err := resolver.MakeResolveParameters(
-		config.GetParameters().Get(), frontingProviderID)
+		config.GetParameters().Get(), frontingProviderID, hostname)
 	if err != nil {
 		return nil, errors.Trace(err)
 	}

+ 18 - 16
psiphon/notice.go

@@ -24,6 +24,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"net"
 	"os"
 	"sort"
 	"strings"
@@ -564,26 +565,27 @@ func noticeWithDialParameters(noticeType string, dialParams *DialParameters, pos
 
 		if dialParams.ResolveParameters != nil {
 
-			if dialParams.ResolveParameters.PreresolvedIPAddress != "" {
-				nonredacted := common.EscapeRedactIPAddressString(dialParams.ResolveParameters.PreresolvedIPAddress)
-				args = append(args, "DNSPreresolved", nonredacted)
-
-			} else {
-
-				// See dialParams.ResolveParameters comment in getBaseAPIParameters.
+			// See dialParams.ResolveParameters comment in getBaseAPIParameters.
 
-				if dialParams.ResolveParameters.PreferAlternateDNSServer {
-					nonredacted := common.EscapeRedactIPAddressString(dialParams.ResolveParameters.AlternateDNSServer)
-					args = append(args, "DNSPreferred", nonredacted)
+			if dialParams.ResolveParameters.PreresolvedIPAddress != "" {
+				meekDialDomain, _, _ := net.SplitHostPort(dialParams.MeekDialAddress)
+				if dialParams.ResolveParameters.PreresolvedDomain == meekDialDomain {
+					nonredacted := common.EscapeRedactIPAddressString(dialParams.ResolveParameters.PreresolvedIPAddress)
+					args = append(args, "DNSPreresolved", nonredacted)
 				}
+			}
 
-				if dialParams.ResolveParameters.ProtocolTransformName != "" {
-					args = append(args, "DNSTransform", dialParams.ResolveParameters.ProtocolTransformName)
-				}
+			if dialParams.ResolveParameters.PreferAlternateDNSServer {
+				nonredacted := common.EscapeRedactIPAddressString(dialParams.ResolveParameters.AlternateDNSServer)
+				args = append(args, "DNSPreferred", nonredacted)
+			}
 
-				if postDial {
-					args = append(args, "DNSAttempt", dialParams.ResolveParameters.GetFirstAttemptWithAnswer())
-				}
+			if dialParams.ResolveParameters.ProtocolTransformName != "" {
+				args = append(args, "DNSTransform", dialParams.ResolveParameters.ProtocolTransformName)
+			}
+
+			if postDial {
+				args = append(args, "DNSAttempt", dialParams.ResolveParameters.GetFirstAttemptWithAnswer())
 			}
 		}
 

+ 46 - 40
psiphon/serverApi.go

@@ -1075,51 +1075,57 @@ func getBaseAPIParameters(
 
 		if dialParams.ResolveParameters != nil {
 
+			// Log enough information to distinguish several successful or
+			// failed circumvention cases of interest, including preferring
+			// alternate servers and/or using DNS protocol transforms, and
+			// appropriate for both handshake and failed_tunnel logging:
+			//
+			// - The initial attempt made by Resolver.ResolveIP,
+			//   preferring an alternate DNS server and/or using a
+			//   protocol transform succeeds (dns_result = 0, the initial
+			//   attempt, 0, got the first result).
+			//
+			// - A second attempt may be used, still preferring an
+			//   alternate DNS server but no longer using the protocol
+			//   transform, which presumably failed (dns_result = 1, the
+			//   second attempt, 1, got the first result).
+			//
+			// - Subsequent attempts will use the system DNS server and no
+			//   protocol transforms (dns_result > 2).
+			//
+			// Due to the design of Resolver.ResolveIP, the notion
+			// of "success" is approximate; for example a successful
+			// response may arrive after a subsequent attempt succeeds,
+			// simply due to slow network conditions. It's also possible
+			// that, for a given attemp, only one of the two concurrent
+			// requests (A and AAAA) succeeded.
+			//
+			// Note that ResolveParameters.GetFirstAttemptWithAnswer
+			// semantics assume that dialParams.ResolveParameters wasn't
+			// used by or modified by any other dial.
+			//
+			// Some protocols may use both preresolved DNS as well as actual
+			// DNS requests, such as Conjure with the DTLS transport, which
+			// may resolve STUN server domains while using preresolved DNS
+			// for fronted API registration.
+
 			if dialParams.ResolveParameters.PreresolvedIPAddress != "" {
-				params["dns_preresolved"] = dialParams.ResolveParameters.PreresolvedIPAddress
-
-			} else {
-
-				// Log enough information to distinguish several successful or
-				// failed circumvention cases of interest, including preferring
-				// alternate servers and/or using DNS protocol transforms, and
-				// appropriate for both handshake and failed_tunnel logging:
-				//
-				// - The initial attempt made by Resolver.ResolveIP,
-				//   preferring an alternate DNS server and/or using a
-				//   protocol transform succeeds (dns_result = 0, the initial
-				//   attempt, 0, got the first result).
-				//
-				// - A second attempt may be used, still preferring an
-				//   alternate DNS server but no longer using the protocol
-				//   transform, which presumably failed (dns_result = 1, the
-				//   second attempt, 1, got the first result).
-				//
-				// - Subsequent attempts will use the system DNS server and no
-				//   protocol transforms (dns_result > 2).
-				//
-				// Due to the design of Resolver.ResolveIP, the notion
-				// of "success" is approximate; for example a successful
-				// response may arrive after a subsequent attempt succeeds,
-				// simply due to slow network conditions. It's also possible
-				// that, for a given attemp, only one of the two concurrent
-				// requests (A and AAAA) succeeded.
-				//
-				// Note that ResolveParameters.GetFirstAttemptWithAnswer
-				// semantics assume that dialParams.ResolveParameters wasn't
-				// used by or modified by any other dial.
-
-				if dialParams.ResolveParameters.PreferAlternateDNSServer {
-					params["dns_preferred"] = dialParams.ResolveParameters.AlternateDNSServer
+				meekDialDomain, _, _ := net.SplitHostPort(dialParams.MeekDialAddress)
+				if dialParams.ResolveParameters.PreresolvedDomain == meekDialDomain {
+					params["dns_preresolved"] = dialParams.ResolveParameters.PreresolvedIPAddress
 				}
+			}
 
-				if dialParams.ResolveParameters.ProtocolTransformName != "" {
-					params["dns_transform"] = dialParams.ResolveParameters.ProtocolTransformName
-				}
+			if dialParams.ResolveParameters.PreferAlternateDNSServer {
+				params["dns_preferred"] = dialParams.ResolveParameters.AlternateDNSServer
+			}
 
-				params["dns_attempt"] = strconv.Itoa(
-					dialParams.ResolveParameters.GetFirstAttemptWithAnswer())
+			if dialParams.ResolveParameters.ProtocolTransformName != "" {
+				params["dns_transform"] = dialParams.ResolveParameters.ProtocolTransformName
 			}
+
+			params["dns_attempt"] = strconv.Itoa(
+				dialParams.ResolveParameters.GetFirstAttemptWithAnswer())
 		}
 
 		if dialParams.HTTPTransformerParameters != nil {

+ 16 - 12
psiphon/tunnel.go

@@ -692,6 +692,9 @@ func dialTunnel(
 	tlsOSSHApplyTrafficShaping := p.WeightedCoinFlip(parameters.TLSTunnelTrafficShapingProbability)
 	tlsOSSHMinTLSPadding := p.Int(parameters.TLSTunnelMinTLSPadding)
 	tlsOSSHMaxTLSPadding := p.Int(parameters.TLSTunnelMaxTLSPadding)
+	conjureEnableIPv6Dials := p.Bool(parameters.ConjureEnableIPv6Dials)
+	conjureEnablePortRandomization := p.Bool(parameters.ConjureEnablePortRandomization)
+	conjureEnableRegistrationOverrides := p.Bool(parameters.ConjureEnableRegistrationOverrides)
 	p.Close()
 
 	// Ensure that, unless the base context is cancelled, any replayed dial
@@ -775,9 +778,7 @@ func dialTunnel(
 	} else if protocol.TunnelProtocolUsesQUIC(dialParams.TunnelProtocol) {
 
 		packetConn, remoteAddr, err := NewUDPConn(
-			ctx,
-			dialParams.DirectDialAddress,
-			dialParams.GetDialConfig())
+			ctx, "udp", false, "", dialParams.DirectDialAddress, dialParams.GetDialConfig())
 		if err != nil {
 			return nil, errors.Trace(err)
 		}
@@ -803,7 +804,7 @@ func dialTunnel(
 			ctx,
 			config.EmitRefractionNetworkingLogs,
 			config.GetPsiphonDataDirectory(),
-			NewNetDialer(dialParams.GetDialConfig()),
+			NewRefractionNetworkingDialer(dialParams.GetDialConfig()).DialContext,
 			dialParams.DirectDialAddress)
 		if err != nil {
 			return nil, errors.Trace(err)
@@ -834,11 +835,15 @@ func dialTunnel(
 		cacheKey := dialParams.NetworkID + "-" + diagnosticID
 
 		conjureConfig := &refraction.ConjureConfig{
-			RegistrationCacheTTL: dialParams.ConjureCachedRegistrationTTL,
-			RegistrationCacheKey: cacheKey,
-			Transport:            dialParams.ConjureTransport,
-			DiagnosticID:         diagnosticID,
-			Logger:               NoticeCommonLogger(),
+			RegistrationCacheTTL:        dialParams.ConjureCachedRegistrationTTL,
+			RegistrationCacheKey:        cacheKey,
+			EnableIPv6Dials:             conjureEnableIPv6Dials,
+			EnablePortRandomization:     conjureEnablePortRandomization,
+			EnableRegistrationOverrides: conjureEnableRegistrationOverrides,
+			Transport:                   dialParams.ConjureTransport,
+			STUNServerAddress:           dialParams.ConjureSTUNServerAddress,
+			DiagnosticID:                diagnosticID,
+			Logger:                      NoticeCommonLogger(),
 		}
 
 		// Set extraFailureAction, which is invoked whenever the tunnel fails (i.e.,
@@ -928,8 +933,7 @@ func dialTunnel(
 			// the decoy registrar connection, like Tapdance, is not, so force it off.
 			// Any tunnel fragmentation metrics will refer to the "phantom" connection
 			// only.
-			conjureConfig.DecoyRegistrarDialer = NewNetDialer(
-				dialParams.GetDialConfig().WithoutFragmentor())
+			conjureConfig.DoDecoyRegistration = true
 			conjureConfig.DecoyRegistrarWidth = dialParams.ConjureDecoyRegistrarWidth
 			conjureConfig.DecoyRegistrarDelay = dialParams.ConjureDecoyRegistrarDelay
 		}
@@ -938,7 +942,7 @@ func dialTunnel(
 			ctx,
 			config.EmitRefractionNetworkingLogs,
 			config.GetPsiphonDataDirectory(),
-			NewNetDialer(dialParams.GetDialConfig()),
+			NewRefractionNetworkingDialer(dialParams.GetDialConfig()).DialContext,
 			dialParams.DirectDialAddress,
 			conjureConfig)
 		if err != nil {

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff