diff --git a/cmd/strelaypoolsrv/main.go b/cmd/strelaypoolsrv/main.go
index 363c2bcb..1f0c9c32 100644
--- a/cmd/strelaypoolsrv/main.go
+++ b/cmd/strelaypoolsrv/main.go
@@ -13,7 +13,6 @@ import (
"fmt"
"io/ioutil"
"log"
- "math/rand"
"mime"
"net"
"net/http"
@@ -29,6 +28,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/syncthing/syncthing/cmd/strelaypoolsrv/auto"
+ "github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/relay/client"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
@@ -370,10 +370,7 @@ func handleGetRequest(w http.ResponseWriter, r *http.Request) {
mut.RUnlock()
// Shuffle
- for i := range relays {
- j := rand.Intn(i + 1)
- relays[i], relays[j] = relays[j], relays[i]
- }
+ rand.Shuffle(relays)
json.NewEncoder(w).Encode(map[string][]*relay{
"relays": relays,
diff --git a/go.mod b/go.mod
index d300429e..3b367275 100644
--- a/go.mod
+++ b/go.mod
@@ -2,10 +2,12 @@ module github.com/syncthing/syncthing
require (
github.com/AudriusButkevicius/go-nat-pmp v0.0.0-20160522074932-452c97607362
+ github.com/AudriusButkevicius/pfilter v0.0.0-20190525131515-730b0de4d4de
github.com/AudriusButkevicius/recli v0.0.5
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e
github.com/calmh/du v1.0.1
github.com/calmh/xdr v1.1.0
+ github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5
github.com/d4l3k/messagediff v1.2.1
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568
@@ -17,10 +19,9 @@ require (
github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657
github.com/kr/pretty v0.1.0 // indirect
github.com/lib/pq v1.1.1
+ github.com/lucas-clemente/quic-go v0.11.1
github.com/mattn/go-isatty v0.0.7
github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338
- github.com/onsi/ginkgo v0.0.0-20171221013426-6c46eb8334b3 // indirect
- github.com/onsi/gomega v0.0.0-20171227184521-ba3724c94e4d // indirect
github.com/oschwald/geoip2-golang v1.3.0
github.com/oschwald/maxminddb-golang v0.0.0-20170901134056-26fe5ace1c70 // indirect
github.com/petermattis/goid v0.0.0-20170816195418-3db12ebb2a59 // indirect
@@ -35,7 +36,7 @@ require (
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc
- golang.org/x/text v0.0.0-20171227012246-e19ae1496984
+ golang.org/x/text v0.3.0
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b
gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
diff --git a/go.sum b/go.sum
index f0f6307a..a2b7c6e2 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
github.com/AudriusButkevicius/go-nat-pmp v0.0.0-20160522074932-452c97607362 h1:l4qGIzSY0WhdXdR74XMYAtfc0Ri/RJVM4p6x/E/+WkA=
github.com/AudriusButkevicius/go-nat-pmp v0.0.0-20160522074932-452c97607362/go.mod h1:CEaBhA5lh1spxbPOELh5wNLKGsVQoahjUhVrJViVK8s=
+github.com/AudriusButkevicius/pfilter v0.0.0-20190525131515-730b0de4d4de h1:w1VG0ehgPh2ucQGO7wL9TBmHLzMo4dduYwyp2lhs8+A=
+github.com/AudriusButkevicius/pfilter v0.0.0-20190525131515-730b0de4d4de/go.mod h1:1N0EEx/irz4B1qV17wW82TFbjQrE7oX316Cki6eDY0Q=
github.com/AudriusButkevicius/recli v0.0.5 h1:xUa55PvWTHBm17T6RvjElRO3y5tALpdceH86vhzQ5wg=
github.com/AudriusButkevicius/recli v0.0.5/go.mod h1:Q2E26yc6RvWWEz/TJ/goUp6yXvipYdJI096hpoaqsNs=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@@ -15,7 +17,11 @@ github.com/calmh/du v1.0.1 h1:uDCrDbXVVPrzxSNRkpj6nqSfwrl5uRWH3zvrJgl7RRo=
github.com/calmh/du v1.0.1/go.mod h1:pHNccp4cXQeyDaiV3S7t5GN+eGOgynF0VSLxJjk9tLU=
github.com/calmh/xdr v1.1.0 h1:U/Dd4CXNLoo8EiQ4ulJUXkgO1/EyQLgDKLgpY1SOoJE=
github.com/calmh/xdr v1.1.0/go.mod h1:E8sz2ByAdXC8MbANf1LCRYzedSnnc+/sXXJs/PVqoeg=
+github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZT9z33pyLEprMd6CUSfhbmMY57Io=
+github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
+github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5 h1:Wg96Dh0MLTanEaPO0OkGtUIaa2jOnShAIOVUIzRHUxo=
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5/go.mod h1:Uc2I36RRfTAf7Dge82bi3RU0OQUmXT9iweIcPqvr8A0=
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
@@ -25,6 +31,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -32,18 +40,20 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/gobwas/glob v0.0.0-20170212200151-51eb1ee00b6d h1:IngNQgbqr5ZOU0exk395Szrvkzes9Ilk1fmJfkw7d+M=
github.com/gobwas/glob v0.0.0-20170212200151-51eb1ee00b6d/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
-github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/groupcache v0.0.0-20171101203131-84a468cf14b4 h1:6o8aP0LGMKzo3NzwhhX6EJsiJ3ejmj+9yA/3p8Fjjlw=
github.com/golang/groupcache v0.0.0-20171101203131-84a468cf14b4/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackpal/gateway v0.0.0-20161225004348-5795ac81146e h1:lS8IitpqG4RkZbEDlZg5Z7FvBdWLVjSVfsPGOKafEkI=
github.com/jackpal/gateway v0.0.0-20161225004348-5795ac81146e/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@@ -58,12 +68,12 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
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/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
-github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
-github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/lucas-clemente/quic-go v0.11.1 h1:zasajC848Dqq/+WqfqBCkmPw+YHNe1MBts/z7y7nXf4=
+github.com/lucas-clemente/quic-go v0.11.1/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
+github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
+github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@@ -72,12 +82,11 @@ github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338 h1:USW1+zAUkUSvk
github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/onsi/ginkgo v0.0.0-20171221013426-6c46eb8334b3 h1:ZN7kHmC0iunA+4UPmERwsuMQan4lUnntO6WX6H1jOO8=
-github.com/onsi/ginkgo v0.0.0-20171221013426-6c46eb8334b3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v0.0.0-20171227184521-ba3724c94e4d h1:r351oUAFgdsydkt/g+XR/iJWRwyxVpy6nkNdEl/QdAs=
-github.com/onsi/gomega v0.0.0-20171227184521-ba3724c94e4d/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/oschwald/geoip2-golang v1.1.0 h1:ACVPz5YqH4/jZkQdsp/PZc9shQVZmreCzAVNss5y3bo=
-github.com/oschwald/geoip2-golang v1.1.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/oschwald/geoip2-golang v1.3.0 h1:D+Hsdos1NARPbzZ2aInUHZL+dApIzo8E0ErJVsWcku8=
github.com/oschwald/geoip2-golang v1.3.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
github.com/oschwald/maxminddb-golang v0.0.0-20170901134056-26fe5ace1c70 h1:XGLYUmodtNzThosQ8GkMvj9TiIB/uWsP8NfxKSa3aDc=
@@ -90,8 +99,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
-github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
@@ -99,13 +106,9 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
-github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
-github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
@@ -128,29 +131,31 @@ github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 h1:okhMind4q9H1OxF44gNegWkiP4H/gsTFLalHFa4OOUI=
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXceWtbTHq6lqcTvYPBKLNejBEbnUsQJtU=
-golang.org/x/crypto v0.0.0-20171231215028-0fcca4842a8d h1:GrqEEc3+MtHKTsZrdIGVoYDgLpbSRzW1EF+nLu0PcHE=
-golang.org/x/crypto v0.0.0-20171231215028-0fcca4842a8d/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM=
-golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/text v0.0.0-20171227012246-e19ae1496984 h1:ulYJn/BqO4fMRe1xAQzWjokgjsQLPpb21GltxXHI3fQ=
-golang.org/x/text v0.0.0-20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
+golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b h1:3X+R0qq1+64izd8es+EttB6qcY+JDlVmAhpRXl7gpzU=
golang.org/x/time v0.0.0-20170927054726-6dc17368e09b/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -160,8 +165,11 @@ gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225/go.mod h1:cuepJuh7vyXfUy
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
-gopkg.in/yaml.v2 v2.0.0-20171116090243-287cf08546ab h1:yZ6iByf7GKeJ3gsd1Dr/xaj1DyJ//wxKX1Cdh8LhoAw=
-gopkg.in/yaml.v2 v2.0.0-20171116090243-287cf08546ab/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/lib/api/mocked_config_test.go b/lib/api/mocked_config_test.go
index cbf8c722..60ddc095 100644
--- a/lib/api/mocked_config_test.go
+++ b/lib/api/mocked_config_test.go
@@ -74,51 +74,55 @@ func (c *mockedConfig) AddOrUpdatePendingDevice(device protocol.DeviceID, name,
func (c *mockedConfig) AddOrUpdatePendingFolder(id, label string, device protocol.DeviceID) {}
-func (m *mockedConfig) MyName() string {
+func (c *mockedConfig) MyName() string {
return ""
}
-func (m *mockedConfig) ConfigPath() string {
+func (c *mockedConfig) ConfigPath() string {
return ""
}
-func (m *mockedConfig) SetGUI(gui config.GUIConfiguration) (config.Waiter, error) {
+func (c *mockedConfig) SetGUI(gui config.GUIConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
-func (m *mockedConfig) SetOptions(opts config.OptionsConfiguration) (config.Waiter, error) {
+func (c *mockedConfig) SetOptions(opts config.OptionsConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
-func (m *mockedConfig) Folder(id string) (config.FolderConfiguration, bool) {
+func (c *mockedConfig) Folder(id string) (config.FolderConfiguration, bool) {
return config.FolderConfiguration{}, false
}
-func (m *mockedConfig) FolderList() []config.FolderConfiguration {
+func (c *mockedConfig) FolderList() []config.FolderConfiguration {
return nil
}
-func (m *mockedConfig) SetFolder(fld config.FolderConfiguration) (config.Waiter, error) {
+func (c *mockedConfig) SetFolder(fld config.FolderConfiguration) (config.Waiter, error) {
return noopWaiter{}, nil
}
-func (m *mockedConfig) Device(id protocol.DeviceID) (config.DeviceConfiguration, bool) {
+func (c *mockedConfig) Device(id protocol.DeviceID) (config.DeviceConfiguration, bool) {
return config.DeviceConfiguration{}, false
}
-func (m *mockedConfig) RemoveDevice(id protocol.DeviceID) (config.Waiter, error) {
+func (c *mockedConfig) RemoveDevice(id protocol.DeviceID) (config.Waiter, error) {
return noopWaiter{}, nil
}
-func (m *mockedConfig) IgnoredDevice(id protocol.DeviceID) bool {
+func (c *mockedConfig) IgnoredDevice(id protocol.DeviceID) bool {
return false
}
-func (m *mockedConfig) IgnoredFolder(device protocol.DeviceID, folder string) bool {
+func (c *mockedConfig) IgnoredFolder(device protocol.DeviceID, folder string) bool {
return false
}
-func (m *mockedConfig) GlobalDiscoveryServers() []string {
+func (c *mockedConfig) GlobalDiscoveryServers() []string {
+ return nil
+}
+
+func (c *mockedConfig) StunServers() []string {
return nil
}
diff --git a/lib/config/config.go b/lib/config/config.go
index 9e1ead57..04e460a4 100644
--- a/lib/config/config.go
+++ b/lib/config/config.go
@@ -39,6 +39,8 @@ const (
var (
// DefaultTCPPort defines default TCP port used if the URI does not specify one, for example tcp://0.0.0.0
DefaultTCPPort = 22000
+ // DefaultQUICPort defines default QUIC port used if the URI does not specify one, for example quic://0.0.0.0
+ DefaultQUICPort = 22000
// DefaultListenAddresses should be substituted when the configuration
// contains default. This is done by the
// "consumer" of the configuration as we don't want these saved to the
@@ -46,6 +48,7 @@ var (
DefaultListenAddresses = []string{
util.Address("tcp", net.JoinHostPort("0.0.0.0", strconv.Itoa(DefaultTCPPort))),
"dynamic+https://relays.syncthing.net/endpoint",
+ util.Address("quic", net.JoinHostPort("0.0.0.0", strconv.Itoa(DefaultQUICPort))),
}
DefaultGUIPort = 8384
// DefaultDiscoveryServersV4 should be substituted when the configuration
@@ -65,6 +68,30 @@ var (
DefaultDiscoveryServers = append(DefaultDiscoveryServersV4, DefaultDiscoveryServersV6...)
// DefaultTheme is the default and fallback theme for the web UI.
DefaultTheme = "default"
+ // Default stun servers should be substituted when the configuration
+ // contains default.
+
+ // DefaultPrimaryStunServers are servers provided by us (to avoid causing the public servers burden)
+ DefaultPrimaryStunServers = []string{
+ "stun.syncthing.net:3478",
+ }
+ DefaultSecondaryStunServers = []string{
+ "stun.callwithus.com:3478",
+ "stun.counterpath.com:3478",
+ "stun.counterpath.net:3478",
+ "stun.ekiga.net:3478",
+ "stun.ideasip.com:3478",
+ "stun.internetcalls.com:3478",
+ "stun.schlund.de:3478",
+ "stun.sipgate.net:10000",
+ "stun.sipgate.net:3478",
+ "stun.voip.aebc.com:3478",
+ "stun.voiparound.com:3478",
+ "stun.voipbuster.com:3478",
+ "stun.voipstunt.com:3478",
+ "stun.voxgratia.org:3478",
+ "stun.xten.com:3478",
+ }
)
func New(myID protocol.DeviceID) Configuration {
@@ -258,8 +285,8 @@ func (cfg *Configuration) clean() error {
existingFolders[folder.ID] = folder
}
- cfg.Options.ListenAddresses = util.UniqueStrings(cfg.Options.ListenAddresses)
- cfg.Options.GlobalAnnServers = util.UniqueStrings(cfg.Options.GlobalAnnServers)
+ cfg.Options.ListenAddresses = util.UniqueTrimmedStrings(cfg.Options.ListenAddresses)
+ cfg.Options.GlobalAnnServers = util.UniqueTrimmedStrings(cfg.Options.GlobalAnnServers)
if cfg.Version > 0 && cfg.Version < OldestHandledVersion {
l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version)
diff --git a/lib/config/config_test.go b/lib/config/config_test.go
index ee6142d8..a227abff 100644
--- a/lib/config/config_test.go
+++ b/lib/config/config_test.go
@@ -69,6 +69,9 @@ func TestDefaultValues(t *testing.T) {
UnackedNotificationIDs: []string{},
DefaultFolderPath: "~",
SetLowPriority: true,
+ StunKeepaliveStartS: 180,
+ StunKeepaliveMinS: 20,
+ StunServers: []string{"default"},
}
cfg := New(device1)
@@ -212,8 +215,11 @@ func TestOverriddenValues(t *testing.T) {
"channelNotification", // added in 17->18 migration
"fsWatcherNotification", // added in 27->28 migration
},
- DefaultFolderPath: "/media/syncthing",
- SetLowPriority: false,
+ DefaultFolderPath: "/media/syncthing",
+ SetLowPriority: false,
+ StunKeepaliveStartS: 9000,
+ StunKeepaliveMinS: 900,
+ StunServers: []string{"foo"},
}
os.Unsetenv("STNOUPGRADE")
diff --git a/lib/config/optionsconfiguration.go b/lib/config/optionsconfiguration.go
index 22e7a6cc..e4da6048 100644
--- a/lib/config/optionsconfiguration.go
+++ b/lib/config/optionsconfiguration.go
@@ -52,6 +52,9 @@ type OptionsConfiguration struct {
DefaultFolderPath string `xml:"defaultFolderPath" json:"defaultFolderPath" default:"~"`
SetLowPriority bool `xml:"setLowPriority" json:"setLowPriority" default:"true"`
MaxConcurrentScans int `xml:"maxConcurrentScans" json:"maxConcurrentScans"`
+ StunKeepaliveStartS int `xml:"stunKeepaliveStartS" json:"stunKeepaliveStartS" default:"180"` // 0 for off
+ StunKeepaliveMinS int `xml:"stunKeepaliveMinS" json:"stunKeepaliveMinS" default:"20"` // 0 for off
+ StunServers []string `xml:"stunServer" json:"stunServers" default:"default"`
DeprecatedUPnPEnabled bool `xml:"upnpEnabled,omitempty" json:"-"`
DeprecatedUPnPLeaseM int `xml:"upnpLeaseMinutes,omitempty" json:"-"`
@@ -61,29 +64,33 @@ type OptionsConfiguration struct {
DeprecatedMinHomeDiskFreePct float64 `xml:"minHomeDiskFreePct,omitempty" json:"-"`
}
-func (orig OptionsConfiguration) Copy() OptionsConfiguration {
- c := orig
- c.ListenAddresses = make([]string, len(orig.ListenAddresses))
- copy(c.ListenAddresses, orig.ListenAddresses)
- c.GlobalAnnServers = make([]string, len(orig.GlobalAnnServers))
- copy(c.GlobalAnnServers, orig.GlobalAnnServers)
- c.AlwaysLocalNets = make([]string, len(orig.AlwaysLocalNets))
- copy(c.AlwaysLocalNets, orig.AlwaysLocalNets)
- c.UnackedNotificationIDs = make([]string, len(orig.UnackedNotificationIDs))
- copy(c.UnackedNotificationIDs, orig.UnackedNotificationIDs)
- return c
+func (opts OptionsConfiguration) Copy() OptionsConfiguration {
+ optsCopy := opts
+ optsCopy.ListenAddresses = make([]string, len(opts.ListenAddresses))
+ copy(optsCopy.ListenAddresses, opts.ListenAddresses)
+ optsCopy.GlobalAnnServers = make([]string, len(opts.GlobalAnnServers))
+ copy(optsCopy.GlobalAnnServers, opts.GlobalAnnServers)
+ optsCopy.AlwaysLocalNets = make([]string, len(opts.AlwaysLocalNets))
+ copy(optsCopy.AlwaysLocalNets, opts.AlwaysLocalNets)
+ optsCopy.UnackedNotificationIDs = make([]string, len(opts.UnackedNotificationIDs))
+ copy(optsCopy.UnackedNotificationIDs, opts.UnackedNotificationIDs)
+ return optsCopy
}
// RequiresRestartOnly returns a copy with only the attributes that require
// restart on change.
-func (orig OptionsConfiguration) RequiresRestartOnly() OptionsConfiguration {
- copy := orig
+func (opts OptionsConfiguration) RequiresRestartOnly() OptionsConfiguration {
+ optsCopy := opts
blank := OptionsConfiguration{}
- util.CopyMatchingTag(&blank, ©, "restart", func(v string) bool {
+ util.CopyMatchingTag(&blank, &optsCopy, "restart", func(v string) bool {
if len(v) > 0 && v != "true" {
- panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "true"`, v))
+ panic(fmt.Sprintf(`unexpected tag value: %s. Expected untagged or "true"`, v))
}
return v != "true"
})
- return copy
+ return optsCopy
+}
+
+func (opts OptionsConfiguration) IsStunDisabled() bool {
+ return opts.StunKeepaliveMinS < 1 || opts.StunKeepaliveStartS < 1 || !opts.NATEnabled
}
diff --git a/lib/config/testdata/overridenvalues.xml b/lib/config/testdata/overridenvalues.xml
index 315f464a..9e3836d3 100644
--- a/lib/config/testdata/overridenvalues.xml
+++ b/lib/config/testdata/overridenvalues.xml
@@ -36,5 +36,8 @@
100
/media/syncthing
false
+ 9000
+ 900
+ foo
diff --git a/lib/config/wrapper.go b/lib/config/wrapper.go
index dabcd581..edd37757 100644
--- a/lib/config/wrapper.go
+++ b/lib/config/wrapper.go
@@ -14,6 +14,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
+ "github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
)
@@ -88,6 +89,7 @@ type Wrapper interface {
ListenAddresses() []string
GlobalDiscoveryServers() []string
+ StunServers() []string
Subscribe(c Committer)
Unsubscribe(c Committer)
@@ -105,6 +107,30 @@ type wrapper struct {
requiresRestart uint32 // an atomic bool
}
+func (w *wrapper) StunServers() []string {
+ var addresses []string
+ for _, addr := range w.cfg.Options.StunServers {
+ switch addr {
+ case "default":
+ defaultPrimaryAddresses := make([]string, len(DefaultPrimaryStunServers))
+ copy(defaultPrimaryAddresses, DefaultPrimaryStunServers)
+ rand.Shuffle(defaultPrimaryAddresses)
+ addresses = append(addresses, defaultPrimaryAddresses...)
+
+ defaultSecondaryAddresses := make([]string, len(DefaultSecondaryStunServers))
+ copy(defaultSecondaryAddresses, DefaultSecondaryStunServers)
+ rand.Shuffle(defaultSecondaryAddresses)
+ addresses = append(addresses, defaultSecondaryAddresses...)
+ default:
+ addresses = append(addresses, addr)
+ }
+ }
+
+ addresses = util.UniqueTrimmedStrings(addresses)
+
+ return addresses
+}
+
// Wrap wraps an existing Configuration structure and ties it to a file on
// disk.
func Wrap(path string, cfg Configuration) Wrapper {
@@ -442,7 +468,7 @@ func (w *wrapper) GlobalDiscoveryServers() []string {
servers = append(servers, srv)
}
}
- return util.UniqueStrings(servers)
+ return util.UniqueTrimmedStrings(servers)
}
func (w *wrapper) ListenAddresses() []string {
@@ -455,7 +481,7 @@ func (w *wrapper) ListenAddresses() []string {
addresses = append(addresses, addr)
}
}
- return util.UniqueStrings(addresses)
+ return util.UniqueTrimmedStrings(addresses)
}
func (w *wrapper) RequiresRestart() bool {
diff --git a/lib/connections/config.go b/lib/connections/config.go
deleted file mode 100644
index 659b3923..00000000
--- a/lib/connections/config.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (C) 2017 The Syncthing Authors.
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this file,
-// You can obtain one at http://mozilla.org/MPL/2.0/.
-
-package connections
-
-const (
- tcpPriority = 10
- relayPriority = 200
-)
diff --git a/lib/connections/quic_dial.go b/lib/connections/quic_dial.go
new file mode 100644
index 00000000..88ff5fa1
--- /dev/null
+++ b/lib/connections/quic_dial.go
@@ -0,0 +1,128 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+// +build go1.12
+
+package connections
+
+import (
+ "context"
+ "crypto/tls"
+ "net"
+ "net/url"
+ "time"
+
+ "github.com/lucas-clemente/quic-go"
+
+ "github.com/syncthing/syncthing/lib/config"
+ "github.com/syncthing/syncthing/lib/connections/registry"
+ "github.com/syncthing/syncthing/lib/protocol"
+)
+
+const quicPriority = 100
+
+func init() {
+ factory := &quicDialerFactory{}
+ for _, scheme := range []string{"quic", "quic4", "quic6"} {
+ dialers[scheme] = factory
+ }
+}
+
+type quicDialer struct {
+ cfg config.Wrapper
+ tlsCfg *tls.Config
+}
+
+func (d *quicDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, error) {
+ uri = fixupPort(uri, config.DefaultQUICPort)
+
+ addr, err := net.ResolveUDPAddr("udp", uri.Host)
+ if err != nil {
+ return internalConn{}, err
+ }
+
+ var conn net.PacketConn
+ closeConn := false
+ if listenConn := registry.Get(uri.Scheme, packetConnLess); listenConn != nil {
+ conn = listenConn.(net.PacketConn)
+ } else {
+ if packetConn, err := net.ListenPacket("udp", ":0"); err != nil {
+ return internalConn{}, err
+ } else {
+ closeConn = true
+ conn = packetConn
+ }
+ }
+
+ ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
+ session, err := quic.DialContext(ctx, conn, addr, uri.Host, d.tlsCfg, quicConfig)
+ if err != nil {
+ if closeConn {
+ _ = conn.Close()
+ }
+ return internalConn{}, err
+ }
+
+ // OpenStreamSync is blocks, but we want to make sure the connection is usable
+ // before we start killing off other connections, so do the dance.
+ ok := make(chan struct{})
+ go func() {
+ select {
+ case <-ok:
+ return
+ case <-time.After(10 * time.Second):
+ l.Debugln("timed out waiting for OpenStream on", session.RemoteAddr())
+ // This will unblock OpenStreamSync
+ _ = session.Close()
+ }
+ }()
+
+ stream, err := session.OpenStreamSync()
+ close(ok)
+ if err != nil {
+ // It's ok to close these, this does not close the underlying packetConn.
+ _ = session.Close()
+ if closeConn {
+ _ = conn.Close()
+ }
+ return internalConn{}, err
+ }
+
+ return internalConn{&quicTlsConn{session, stream}, connTypeQUICClient, quicPriority}, nil
+}
+
+func (d *quicDialer) RedialFrequency() time.Duration {
+ return time.Duration(d.cfg.Options().ReconnectIntervalS) * time.Second
+}
+
+type quicDialerFactory struct {
+ cfg config.Wrapper
+ tlsCfg *tls.Config
+}
+
+func (quicDialerFactory) New(cfg config.Wrapper, tlsCfg *tls.Config) genericDialer {
+ return &quicDialer{
+ cfg: cfg,
+ tlsCfg: tlsCfg,
+ }
+}
+
+func (quicDialerFactory) Priority() int {
+ return quicPriority
+}
+
+func (quicDialerFactory) AlwaysWAN() bool {
+ return false
+}
+
+func (quicDialerFactory) Valid(_ config.Configuration) error {
+ // Always valid
+ return nil
+}
+
+func (quicDialerFactory) String() string {
+ return "QUIC Dialer"
+}
diff --git a/lib/connections/quic_listen.go b/lib/connections/quic_listen.go
new file mode 100644
index 00000000..a192008c
--- /dev/null
+++ b/lib/connections/quic_listen.go
@@ -0,0 +1,238 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// +build go1.12
+
+package connections
+
+import (
+ "crypto/tls"
+ "net"
+ "net/url"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/lucas-clemente/quic-go"
+
+ "github.com/syncthing/syncthing/lib/config"
+ "github.com/syncthing/syncthing/lib/connections/registry"
+ "github.com/syncthing/syncthing/lib/nat"
+ "github.com/syncthing/syncthing/lib/stun"
+)
+
+func init() {
+ factory := &quicListenerFactory{}
+ for _, scheme := range []string{"quic", "quic4", "quic6"} {
+ listeners[scheme] = factory
+ }
+}
+
+type quicListener struct {
+ nat atomic.Value
+
+ onAddressesChangedNotifier
+
+ uri *url.URL
+ cfg config.Wrapper
+ tlsCfg *tls.Config
+ stop chan struct{}
+ conns chan internalConn
+ factory listenerFactory
+
+ address *url.URL
+ err error
+ mut sync.Mutex
+}
+
+func (t *quicListener) OnNATTypeChanged(natType stun.NATType) {
+ if natType != stun.NATUnknown {
+ l.Infof("%s detected NAT type: %s", t.uri, natType)
+ }
+ t.nat.Store(natType)
+}
+
+func (t *quicListener) OnExternalAddressChanged(address *stun.Host, via string) {
+ var uri *url.URL
+ if address != nil {
+ uri = &(*t.uri)
+ uri.Host = address.TransportAddr()
+ }
+
+ t.mut.Lock()
+ existingAddress := t.address
+ t.address = uri
+ t.mut.Unlock()
+
+ if uri != nil && (existingAddress == nil || existingAddress.String() != uri.String()) {
+ l.Infof("%s resolved external address %s (via %s)", t.uri, uri.String(), via)
+ t.notifyAddressesChanged(t)
+ } else if uri == nil && existingAddress != nil {
+ t.notifyAddressesChanged(t)
+ }
+}
+
+func (t *quicListener) Serve() {
+ t.mut.Lock()
+ t.err = nil
+ t.mut.Unlock()
+
+ network := strings.Replace(t.uri.Scheme, "quic", "udp", -1)
+
+ packetConn, err := net.ListenPacket(network, t.uri.Host)
+ if err != nil {
+ t.mut.Lock()
+ t.err = err
+ t.mut.Unlock()
+ l.Infoln("Listen (BEP/quic):", err)
+ return
+ }
+ defer func() { _ = packetConn.Close() }()
+
+ svc, conn := stun.New(t.cfg, t, packetConn)
+ defer func() { _ = conn.Close() }()
+
+ go svc.Serve()
+ defer svc.Stop()
+
+ registry.Register(t.uri.Scheme, conn)
+ defer registry.Unregister(t.uri.Scheme, conn)
+
+ listener, err := quic.Listen(conn, t.tlsCfg, quicConfig)
+ if err != nil {
+ t.mut.Lock()
+ t.err = err
+ t.mut.Unlock()
+ l.Infoln("Listen (BEP/quic):", err)
+ return
+ }
+
+ l.Infof("QUIC listener (%v) starting", packetConn.LocalAddr())
+ defer l.Infof("QUIC listener (%v) shutting down", packetConn.LocalAddr())
+
+ // Accept is forever, so handle stops externally.
+ go func() {
+ select {
+ case <-t.stop:
+ _ = listener.Close()
+ }
+ }()
+
+ for {
+ // Blocks forever, see https://github.com/lucas-clemente/quic-go/issues/1915
+ session, err := listener.Accept()
+
+ select {
+ case <-t.stop:
+ if err == nil {
+ _ = session.Close()
+ }
+ return
+ default:
+ }
+ if err != nil {
+ if err, ok := err.(net.Error); !ok || !err.Timeout() {
+ l.Warnln("Listen (BEP/quic): Accepting connection:", err)
+ }
+ continue
+ }
+
+ l.Debugln("connect from", session.RemoteAddr())
+
+ // Accept blocks forever, give it 10s to do it's thing.
+ ok := make(chan struct{})
+ go func() {
+ select {
+ case <-ok:
+ return
+ case <-t.stop:
+ _ = session.Close()
+ case <-time.After(10 * time.Second):
+ l.Debugln("timed out waiting for AcceptStream on", session.RemoteAddr())
+ _ = session.Close()
+ }
+ }()
+
+ stream, err := session.AcceptStream()
+ close(ok)
+ if err != nil {
+ l.Debugln("failed to accept stream from", session.RemoteAddr(), err.Error())
+ _ = session.Close()
+ continue
+ }
+
+ t.conns <- internalConn{&quicTlsConn{session, stream}, connTypeQUICServer, quicPriority}
+ }
+}
+
+func (t *quicListener) Stop() {
+ close(t.stop)
+}
+
+func (t *quicListener) URI() *url.URL {
+ return t.uri
+}
+
+func (t *quicListener) WANAddresses() []*url.URL {
+ uris := t.LANAddresses()
+ t.mut.Lock()
+ if t.address != nil {
+ uris = append(uris, t.address)
+ }
+ t.mut.Unlock()
+ return uris
+}
+
+func (t *quicListener) LANAddresses() []*url.URL {
+ return []*url.URL{t.uri}
+}
+
+func (t *quicListener) Error() error {
+ t.mut.Lock()
+ err := t.err
+ t.mut.Unlock()
+ return err
+}
+
+func (t *quicListener) String() string {
+ return t.uri.String()
+}
+
+func (t *quicListener) Factory() listenerFactory {
+ return t.factory
+}
+
+func (t *quicListener) NATType() string {
+ v := t.nat.Load().(stun.NATType)
+ if v == stun.NATUnknown || v == stun.NATError {
+ return "unknown"
+ }
+ return v.String()
+}
+
+type quicListenerFactory struct{}
+
+func (f *quicListenerFactory) Valid(config.Configuration) error {
+ return nil
+}
+
+func (f *quicListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.Config, conns chan internalConn, natService *nat.Service) genericListener {
+ l := &quicListener{
+ uri: fixupPort(uri, config.DefaultQUICPort),
+ cfg: cfg,
+ tlsCfg: tlsCfg,
+ conns: conns,
+ stop: make(chan struct{}),
+ factory: f,
+ }
+ l.nat.Store(stun.NATUnknown)
+ return l
+}
+
+func (quicListenerFactory) Enabled(cfg config.Configuration) bool {
+ return true
+}
diff --git a/lib/connections/quic_misc.go b/lib/connections/quic_misc.go
new file mode 100644
index 00000000..93c08600
--- /dev/null
+++ b/lib/connections/quic_misc.go
@@ -0,0 +1,57 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// +build go1.12
+
+package connections
+
+import (
+ "net"
+
+ "github.com/lucas-clemente/quic-go"
+)
+
+var (
+ quicConfig = &quic.Config{
+ ConnectionIDLength: 4,
+ KeepAlive: true,
+ }
+)
+
+type quicTlsConn struct {
+ quic.Session
+ quic.Stream
+}
+
+func (q *quicTlsConn) Close() error {
+ sterr := q.Stream.Close()
+ seerr := q.Session.Close()
+ if sterr != nil {
+ return sterr
+ }
+ return seerr
+}
+
+// Sort available packet connections by ip address, preferring unspecified local address.
+func packetConnLess(i interface{}, j interface{}) bool {
+ iIsUnspecified := false
+ jIsUnspecified := false
+ iLocalAddr := i.(net.PacketConn).LocalAddr()
+ jLocalAddr := j.(net.PacketConn).LocalAddr()
+
+ if host, _, err := net.SplitHostPort(iLocalAddr.String()); err == nil {
+ iIsUnspecified = host == "" || net.ParseIP(host).IsUnspecified()
+ }
+ if host, _, err := net.SplitHostPort(jLocalAddr.String()); err == nil {
+ jIsUnspecified = host == "" || net.ParseIP(host).IsUnspecified()
+ }
+
+ if jIsUnspecified == iIsUnspecified {
+ return len(iLocalAddr.Network()) < len(jLocalAddr.Network())
+ }
+
+ return iIsUnspecified
+}
diff --git a/lib/connections/quic_misc_test.go b/lib/connections/quic_misc_test.go
new file mode 100644
index 00000000..726e5e20
--- /dev/null
+++ b/lib/connections/quic_misc_test.go
@@ -0,0 +1,92 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// +build go1.12
+
+package connections
+
+import (
+ "net"
+ "testing"
+ "time"
+)
+
+type mockPacketConn struct {
+ addr mockedAddr
+}
+
+func (mockPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
+ panic("implement me")
+}
+
+func (mockPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
+ panic("implement me")
+}
+
+func (mockPacketConn) Close() error {
+ panic("implement me")
+}
+
+func (c *mockPacketConn) LocalAddr() net.Addr {
+ return c.addr
+}
+
+func (mockPacketConn) SetDeadline(t time.Time) error {
+ panic("implement me")
+}
+
+func (mockPacketConn) SetReadDeadline(t time.Time) error {
+ panic("implement me")
+}
+
+func (mockPacketConn) SetWriteDeadline(t time.Time) error {
+ panic("implement me")
+}
+
+type mockedAddr struct {
+ network string
+ addr string
+}
+
+func (a mockedAddr) Network() string {
+ return a.network
+}
+
+func (a mockedAddr) String() string {
+ return a.addr
+}
+
+func TestPacketConnLess(t *testing.T) {
+ cases := []struct {
+ netA string
+ addrA string
+ netB string
+ addrB string
+ }{
+ // B is assumed the winner.
+ {"tcp", "127.0.0.1:1234", "tcp", ":1235"},
+ {"tcp", "127.0.0.1:1234", "tcp", "0.0.0.0:1235"},
+ {"tcp4", "0.0.0.0:1234", "tcp", "0.0.0.0:1235"}, // tcp4 on the first one
+ }
+
+ for i, testCase := range cases {
+
+ conns := []*mockPacketConn{
+ {mockedAddr{testCase.netA, testCase.addrA}},
+ {mockedAddr{testCase.netB, testCase.addrB}},
+ }
+
+ if packetConnLess(conns[0], conns[1]) {
+ t.Error(i, "unexpected")
+ }
+ if !packetConnLess(conns[1], conns[0]) {
+ t.Error(i, "unexpected")
+ }
+ if packetConnLess(conns[0], conns[0]) || packetConnLess(conns[1], conns[1]) {
+ t.Error(i, "unexpected")
+ }
+ }
+}
diff --git a/lib/connections/registry/registry.go b/lib/connections/registry/registry.go
new file mode 100644
index 00000000..42dcf699
--- /dev/null
+++ b/lib/connections/registry/registry.go
@@ -0,0 +1,89 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+// Registry tracks connections/addresses on which we are listening on, to allow us to pick a connection/address that
+// has a NAT port mapping. This also makes our outgoing port stable and same as incoming port which should allow
+// better probability of punching through.
+package registry
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/syncthing/syncthing/lib/sync"
+)
+
+var (
+ Default = New()
+)
+
+type Registry struct {
+ mut sync.Mutex
+ available map[string][]interface{}
+}
+
+func New() *Registry {
+ return &Registry{
+ mut: sync.NewMutex(),
+ available: make(map[string][]interface{}),
+ }
+}
+
+func (r *Registry) Register(scheme string, item interface{}) {
+ r.mut.Lock()
+ defer r.mut.Unlock()
+
+ r.available[scheme] = append(r.available[scheme], item)
+}
+
+func (r *Registry) Unregister(scheme string, item interface{}) {
+ r.mut.Lock()
+ defer r.mut.Unlock()
+
+ candidates := r.available[scheme]
+ for i, existingItem := range candidates {
+ if existingItem == item {
+ copy(candidates[i:], candidates[i+1:])
+ candidates[len(candidates)-1] = nil
+ r.available[scheme] = candidates[:len(candidates)-1]
+ break
+ }
+ }
+}
+
+func (r *Registry) Get(scheme string, less func(i, j interface{}) bool) interface{} {
+ r.mut.Lock()
+ defer r.mut.Unlock()
+
+ candidates := make([]interface{}, 0)
+ for availableScheme, items := range r.available {
+ // quic:// should be considered ok for both quic4:// and quic6://
+ if strings.HasPrefix(scheme, availableScheme) {
+ candidates = append(candidates, items...)
+ }
+ }
+
+ if len(candidates) == 0 {
+ return nil
+ }
+
+ sort.Slice(candidates, func(i, j int) bool {
+ return less(candidates[i], candidates[j])
+ })
+ return candidates[0]
+}
+
+func Register(scheme string, item interface{}) {
+ Default.Register(scheme, item)
+}
+
+func Unregister(scheme string, item interface{}) {
+ Default.Unregister(scheme, item)
+}
+
+func Get(scheme string, less func(i, j interface{}) bool) interface{} {
+ return Default.Get(scheme, less)
+}
diff --git a/lib/connections/registry/registry_test.go b/lib/connections/registry/registry_test.go
new file mode 100644
index 00000000..f62a5f5c
--- /dev/null
+++ b/lib/connections/registry/registry_test.go
@@ -0,0 +1,71 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package registry
+
+import (
+ "testing"
+)
+
+func TestRegistry(t *testing.T) {
+ r := New()
+
+ if res := r.Get("int", intLess); res != nil {
+ t.Error("unexpected")
+ }
+
+ r.Register("int", 1)
+ r.Register("int", 11)
+ r.Register("int4", 4)
+ r.Register("int4", 44)
+ r.Register("int6", 6)
+ r.Register("int6", 66)
+
+ if res := r.Get("int", intLess).(int); res != 1 {
+ t.Error("unexpected", res)
+ }
+
+ // int is prefix of int4, so returns 1
+ if res := r.Get("int4", intLess).(int); res != 1 {
+ t.Error("unexpected", res)
+ }
+
+ r.Unregister("int", 1)
+
+ // Check that falls through to 11
+ if res := r.Get("int", intLess).(int); res != 11 {
+ t.Error("unexpected", res)
+ }
+
+ // 6 is smaller than 11 available in int.
+ if res := r.Get("int6", intLess).(int); res != 6 {
+ t.Error("unexpected", res)
+ }
+
+ // Unregister 11, int should be impossible to find
+ r.Unregister("int", 11)
+ if res := r.Get("int", intLess); res != nil {
+ t.Error("unexpected")
+ }
+
+ // Unregister a second time does nothing.
+ r.Unregister("int", 1)
+
+ // Can have multiple of the same
+ r.Register("int", 1)
+ r.Register("int", 1)
+ r.Unregister("int", 1)
+
+ if res := r.Get("int4", intLess).(int); res != 1 {
+ t.Error("unexpected", res)
+ }
+}
+
+func intLess(i, j interface{}) bool {
+ iInt := i.(int)
+ jInt := j.(int)
+ return iInt < jInt
+}
diff --git a/lib/connections/relay_dial.go b/lib/connections/relay_dial.go
index ab236e28..3ba5558b 100644
--- a/lib/connections/relay_dial.go
+++ b/lib/connections/relay_dial.go
@@ -17,6 +17,8 @@ import (
"github.com/syncthing/syncthing/lib/relay/client"
)
+const relayPriority = 200
+
func init() {
dialers["relay"] = relayDialerFactory{}
}
diff --git a/lib/connections/service.go b/lib/connections/service.go
index 95547f2e..02b6b15b 100644
--- a/lib/connections/service.go
+++ b/lib/connections/service.go
@@ -375,7 +375,7 @@ func (s *service) connect() {
}
}
- addrs = util.UniqueStrings(addrs)
+ addrs = util.UniqueTrimmedStrings(addrs)
l.Debugln("Reconnect loop for", deviceID, addrs)
@@ -642,7 +642,7 @@ func (s *service) AllAddresses() []string {
}
}
s.listenersMut.RUnlock()
- return util.UniqueStrings(addrs)
+ return util.UniqueTrimmedStrings(addrs)
}
func (s *service) ExternalAddresses() []string {
@@ -654,7 +654,7 @@ func (s *service) ExternalAddresses() []string {
}
}
s.listenersMut.RUnlock()
- return util.UniqueStrings(addrs)
+ return util.UniqueTrimmedStrings(addrs)
}
func (s *service) ListenerStatus() map[string]ListenerStatusEntry {
diff --git a/lib/connections/structs.go b/lib/connections/structs.go
index cdb14c08..8cffbaff 100644
--- a/lib/connections/structs.go
+++ b/lib/connections/structs.go
@@ -9,6 +9,7 @@ package connections
import (
"crypto/tls"
"fmt"
+ "io"
"net"
"net/url"
"time"
@@ -43,10 +44,19 @@ func (c completeConn) Close(err error) {
c.internalConn.Close()
}
+type tlsConn interface {
+ io.ReadWriteCloser
+ ConnectionState() tls.ConnectionState
+ RemoteAddr() net.Addr
+ SetDeadline(time.Time) error
+ SetWriteDeadline(time.Time) error
+ LocalAddr() net.Addr
+}
+
// internalConn is the raw TLS connection plus some metadata on where it
// came from (type, priority).
type internalConn struct {
- *tls.Conn
+ tlsConn
connType connType
priority int
}
@@ -58,6 +68,8 @@ const (
connTypeRelayServer
connTypeTCPClient
connTypeTCPServer
+ connTypeQUICClient
+ connTypeQUICServer
)
func (t connType) String() string {
@@ -70,6 +82,10 @@ func (t connType) String() string {
return "tcp-client"
case connTypeTCPServer:
return "tcp-server"
+ case connTypeQUICClient:
+ return "quic-client"
+ case connTypeQUICServer:
+ return "quic-server"
default:
return "unknown-type"
}
@@ -81,6 +97,8 @@ func (t connType) Transport() string {
return "relay"
case connTypeTCPClient, connTypeTCPServer:
return "tcp"
+ case connTypeQUICClient, connTypeQUICServer:
+ return "quic"
default:
return "unknown"
}
@@ -90,8 +108,8 @@ func (c internalConn) Close() {
// *tls.Conn.Close() does more than it says on the tin. Specifically, it
// sends a TLS alert message, which might block forever if the
// connection is dead and we don't have a deadline set.
- c.SetWriteDeadline(time.Now().Add(250 * time.Millisecond))
- c.Conn.Close()
+ _ = c.SetWriteDeadline(time.Now().Add(250 * time.Millisecond))
+ _ = c.tlsConn.Close()
}
func (c internalConn) Type() string {
diff --git a/lib/connections/tcp_dial.go b/lib/connections/tcp_dial.go
index 618e772b..db083e45 100644
--- a/lib/connections/tcp_dial.go
+++ b/lib/connections/tcp_dial.go
@@ -16,6 +16,8 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
)
+const tcpPriority = 10
+
func init() {
factory := &tcpDialerFactory{}
for _, scheme := range []string{"tcp", "tcp4", "tcp6"} {
diff --git a/lib/discover/cache.go b/lib/discover/cache.go
index 2fb28169..230d89fd 100644
--- a/lib/discover/cache.go
+++ b/lib/discover/cache.go
@@ -7,6 +7,7 @@
package discover
import (
+ "sort"
stdsync "sync"
"time"
@@ -121,11 +122,12 @@ func (m *cachingMux) Lookup(deviceID protocol.DeviceID) (addresses []string, err
}
m.mut.RUnlock()
+ addresses = util.UniqueTrimmedStrings(addresses)
+ sort.Strings(addresses)
+
l.Debugln("lookup results for", deviceID)
l.Debugln(" addresses: ", addresses)
- addresses = util.UniqueStrings(addresses)
-
return addresses, nil
}
@@ -185,7 +187,7 @@ func (m *cachingMux) Cache() map[protocol.DeviceID]CacheEntry {
m.mut.RUnlock()
for k, v := range res {
- v.Addresses = util.UniqueStrings(v.Addresses)
+ v.Addresses = util.UniqueTrimmedStrings(v.Addresses)
res[k] = v
}
diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go
index 584a61e5..c01b34cf 100644
--- a/lib/model/folder_sendrecv.go
+++ b/lib/model/folder_sendrecv.go
@@ -9,7 +9,6 @@ package model
import (
"bytes"
"fmt"
- "math/rand"
"path/filepath"
"runtime"
"sort"
@@ -25,6 +24,7 @@ import (
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
+ "github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/scanner"
"github.com/syncthing/syncthing/lib/sha256"
"github.com/syncthing/syncthing/lib/sync"
@@ -1089,10 +1089,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
}
// Shuffle the blocks
- for i := range blocks {
- j := rand.Intn(i + 1)
- blocks[i], blocks[j] = blocks[j], blocks[i]
- }
+ rand.Shuffle(blocks)
events.Default.Log(events.ItemStarted, map[string]string{
"folder": f.folderID,
diff --git a/lib/model/queue.go b/lib/model/queue.go
index e5287982..15d13914 100644
--- a/lib/model/queue.go
+++ b/lib/model/queue.go
@@ -7,10 +7,10 @@
package model
import (
- "math/rand"
"sort"
"time"
+ "github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -103,11 +103,7 @@ func (q *jobQueue) Shuffle() {
q.mut.Lock()
defer q.mut.Unlock()
- l := len(q.queued)
- for i := range q.queued {
- r := rand.Intn(l)
- q.queued[i], q.queued[r] = q.queued[r], q.queued[i]
- }
+ rand.Shuffle(q.queued)
}
func (q *jobQueue) Reset() {
diff --git a/lib/rand/random.go b/lib/rand/random.go
index 23a2f255..262669e0 100644
--- a/lib/rand/random.go
+++ b/lib/rand/random.go
@@ -14,6 +14,7 @@ import (
"encoding/binary"
"io"
mathRand "math/rand"
+ "reflect"
)
// Reader is the standard crypto/rand.Reader, re-exported for convenience
@@ -73,3 +74,14 @@ func SeedFromBytes(bs []byte) int64 {
// uint64s and XOR them together.
return int64(binary.BigEndian.Uint64(s[0:]) ^ binary.BigEndian.Uint64(s[8:]))
}
+
+// Shuffle the order of elements
+func Shuffle(slice interface{}) {
+ rv := reflect.ValueOf(slice)
+ swap := reflect.Swapper(slice)
+ length := rv.Len()
+ if length < 2 {
+ return
+ }
+ defaultSecureRand.Shuffle(length, swap)
+}
diff --git a/lib/relay/client/dynamic.go b/lib/relay/client/dynamic.go
index 5e83c514..17400b78 100644
--- a/lib/relay/client/dynamic.go
+++ b/lib/relay/client/dynamic.go
@@ -6,13 +6,13 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
- "math/rand"
"net/http"
"net/url"
"sort"
"time"
"github.com/syncthing/syncthing/lib/osutil"
+ "github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/relay/protocol"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -209,7 +209,7 @@ func relayAddressesOrder(input []string) []string {
var ids []int
for id, bucket := range buckets {
- shuffle(bucket)
+ rand.Shuffle(bucket)
ids = append(ids, id)
}
@@ -223,10 +223,3 @@ func relayAddressesOrder(input []string) []string {
return addresses
}
-
-func shuffle(slice []string) {
- for i := len(slice) - 1; i > 0; i-- {
- j := rand.Intn(i + 1)
- slice[i], slice[j] = slice[j], slice[i]
- }
-}
diff --git a/lib/stun/debug.go b/lib/stun/debug.go
new file mode 100644
index 00000000..196dccef
--- /dev/null
+++ b/lib/stun/debug.go
@@ -0,0 +1,22 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package stun
+
+import (
+ "os"
+ "strings"
+
+ "github.com/syncthing/syncthing/lib/logger"
+)
+
+var (
+ l = logger.DefaultLogger.NewFacility("stun", "STUN functionality")
+)
+
+func init() {
+ l.SetDebug("stun", strings.Contains(os.Getenv("STTRACE"), "stun") || os.Getenv("STTRACE") == "all")
+}
diff --git a/lib/stun/filter.go b/lib/stun/filter.go
new file mode 100644
index 00000000..8be3bd4d
--- /dev/null
+++ b/lib/stun/filter.go
@@ -0,0 +1,64 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package stun
+
+import (
+ "bytes"
+ "net"
+ "sync"
+ "time"
+)
+
+const (
+ stunFilterPriority = 10
+ otherDataPriority = 100
+)
+
+type stunFilter struct {
+ ids map[string]time.Time
+ mut sync.Mutex
+}
+
+func (f *stunFilter) Outgoing(out []byte, addr net.Addr) {
+ if !f.isStunPayload(out) {
+ panic("not a stun payload")
+ }
+ f.mut.Lock()
+ f.ids[string(out[8:20])] = time.Now().Add(time.Minute)
+ f.reap()
+ f.mut.Unlock()
+}
+
+func (f *stunFilter) ClaimIncoming(in []byte, addr net.Addr) bool {
+ if f.isStunPayload(in) {
+ f.mut.Lock()
+ _, ok := f.ids[string(in[8:20])]
+ f.reap()
+ f.mut.Unlock()
+ return ok
+ }
+ return false
+}
+
+func (f *stunFilter) isStunPayload(data []byte) bool {
+ // Need at least 20 bytes
+ if len(data) < 20 {
+ return false
+ }
+
+ // First two bits always unset, and should always send magic cookie.
+ return data[0]&0xc0 == 0 && bytes.Equal(data[4:8], []byte{0x21, 0x12, 0xA4, 0x42})
+}
+
+func (f *stunFilter) reap() {
+ now := time.Now()
+ for id, timeout := range f.ids {
+ if timeout.Before(now) {
+ delete(f.ids, id)
+ }
+ }
+}
diff --git a/lib/stun/stun.go b/lib/stun/stun.go
new file mode 100644
index 00000000..bcf18e75
--- /dev/null
+++ b/lib/stun/stun.go
@@ -0,0 +1,310 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package stun
+
+import (
+ "net"
+ "sync/atomic"
+ "time"
+
+ "github.com/AudriusButkevicius/pfilter"
+ "github.com/ccding/go-stun/stun"
+ "github.com/syncthing/syncthing/lib/config"
+)
+
+const stunRetryInterval = 5 * time.Minute
+
+type Host = stun.Host
+type NATType = stun.NATType
+
+// NAT types.
+
+const (
+ NATError = stun.NATError
+ NATUnknown = stun.NATUnknown
+ NATNone = stun.NATNone
+ NATBlocked = stun.NATBlocked
+ NATFull = stun.NATFull
+ NATSymmetric = stun.NATSymmetric
+ NATRestricted = stun.NATRestricted
+ NATPortRestricted = stun.NATPortRestricted
+ NATSymmetricUDPFirewall = stun.NATSymmetricUDPFirewall
+)
+
+type writeTrackingPacketConn struct {
+ lastWrite int64
+ net.PacketConn
+}
+
+func (c *writeTrackingPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
+ atomic.StoreInt64(&c.lastWrite, time.Now().Unix())
+ return c.PacketConn.WriteTo(p, addr)
+}
+
+func (c *writeTrackingPacketConn) getLastWrite() time.Time {
+ unix := atomic.LoadInt64(&c.lastWrite)
+ return time.Unix(unix, 0)
+}
+
+type Subscriber interface {
+ OnNATTypeChanged(natType NATType)
+ OnExternalAddressChanged(address *Host, via string)
+}
+
+type Service struct {
+ name string
+ cfg config.Wrapper
+ subscriber Subscriber
+ stunConn net.PacketConn
+ client *stun.Client
+
+ writeTrackingPacketConn *writeTrackingPacketConn
+
+ natType NATType
+ addr *Host
+
+ stop chan struct{}
+}
+
+func New(cfg config.Wrapper, subscriber Subscriber, conn net.PacketConn) (*Service, net.PacketConn) {
+ // Wrap the original connection to track writes on it
+ writeTrackingPacketConn := &writeTrackingPacketConn{lastWrite: 0, PacketConn: conn}
+
+ // Wrap it in a filter and split it up, so that stun packets arrive on stun conn, others arrive on the data conn
+ filterConn := pfilter.NewPacketFilter(writeTrackingPacketConn)
+ otherDataConn := filterConn.NewConn(otherDataPriority, nil)
+ stunConn := filterConn.NewConn(stunFilterPriority, &stunFilter{
+ ids: make(map[string]time.Time),
+ })
+
+ filterConn.Start()
+
+ // Construct the client to use the stun conn
+ client := stun.NewClientWithConnection(stunConn)
+ client.SetSoftwareName("") // Explicitly unset this, seems to freak some servers out.
+
+ // Return the service and the other conn to the client
+ return &Service{
+ name: "Stun@" + conn.LocalAddr().Network() + "://" + conn.LocalAddr().String(),
+
+ cfg: cfg,
+ subscriber: subscriber,
+ stunConn: stunConn,
+ client: client,
+
+ writeTrackingPacketConn: writeTrackingPacketConn,
+
+ natType: NATUnknown,
+ addr: nil,
+ stop: make(chan struct{}),
+ }, otherDataConn
+}
+
+func (s *Service) Stop() {
+ close(s.stop)
+ _ = s.stunConn.Close()
+}
+
+func (s *Service) Serve() {
+ for {
+ disabled:
+ s.setNATType(NATUnknown)
+ s.setExternalAddress(nil, "")
+
+ if s.cfg.Options().IsStunDisabled() {
+ select {
+ case <-s.stop:
+ return
+ case <-time.After(time.Second):
+ continue
+ }
+ }
+
+ l.Debugf("Starting stun for %s", s)
+
+ for _, addr := range s.cfg.StunServers() {
+ // This blocks until we hit an exit condition or there are issues with the STUN server.
+ // This returns a boolean signifying if a different STUN server should be tried (oppose to the whole thing
+ // shutting down and this winding itself down.
+ if !s.runStunForServer(addr) {
+ // Check exit conditions.
+
+ // Have we been asked to stop?
+ select {
+ case <-s.stop:
+ return
+ default:
+ }
+
+ // Are we disabled?
+ if s.cfg.Options().IsStunDisabled() {
+ l.Infoln("STUN disabled")
+ goto disabled
+ }
+
+ // Unpunchable NAT? Chillout for some time.
+ if !s.isCurrentNATTypePunchable() {
+ break
+ }
+ }
+ }
+
+ // Failed all servers, sad.
+ s.setNATType(NATUnknown)
+ s.setExternalAddress(nil, "")
+
+ // We failed to contact all provided stun servers or the nat is not punchable.
+ // Chillout for a while.
+ time.Sleep(stunRetryInterval)
+ }
+}
+
+func (s *Service) runStunForServer(addr string) (tryNext bool) {
+ l.Debugf("Running stun for %s via %s", s, addr)
+
+ // Resolve the address, so that in case the server advertises two
+ // IPs, we always hit the same one, as otherwise, the mapping might
+ // expire as we hit the other address, and cause us to flip flop
+ // between servers/external addresses, as a result flooding discovery
+ // servers.
+ udpAddr, err := net.ResolveUDPAddr("udp", addr)
+ if err != nil {
+ l.Debugf("%s stun addr resolution on %s: %s", s, addr, err)
+ return true
+ }
+ s.client.SetServerAddr(udpAddr.String())
+
+ natType, extAddr, err := s.client.Discover()
+ if err != nil || extAddr == nil {
+ l.Debugf("%s stun discovery on %s: %s", s, addr, err)
+ return true
+ }
+
+ // The stun server is most likely borked, try another one.
+ if natType == NATError || natType == NATUnknown || natType == NATBlocked {
+ l.Debugf("%s stun discovery on %s resolved to %s", s, addr, natType)
+ return true
+ }
+
+ s.setNATType(natType)
+ s.setExternalAddress(extAddr, addr)
+ l.Debugf("%s detected NAT type: %s via %s", s, natType, addr)
+
+ // We can't punch through this one, so no point doing keepalives
+ // and such, just let the caller check the nat type and work it out themselves.
+ if !s.isCurrentNATTypePunchable() {
+ l.Debugf("%s cannot punch %s, skipping", s, natType)
+ return false
+ }
+
+ return s.stunKeepAlive(addr, extAddr)
+}
+
+func (s *Service) stunKeepAlive(addr string, extAddr *Host) (tryNext bool) {
+ var err error
+ nextSleep := time.Duration(s.cfg.Options().StunKeepaliveStartS) * time.Second
+
+ l.Debugf("%s starting stun keepalive via %s, next sleep %s", s, addr, nextSleep)
+
+ for {
+ if areDifferent(s.addr, extAddr) {
+ // If the port has changed (addresses are not equal but the hosts are equal),
+ // we're probably spending too much time between keepalives, reduce the sleep.
+ if s.addr != nil && extAddr != nil && s.addr.IP() == extAddr.IP() {
+ nextSleep /= 2
+ l.Debugf("%s stun port change (%s to %s), next sleep %s", s, s.addr.TransportAddr(), extAddr.TransportAddr(), nextSleep)
+ }
+
+ s.setExternalAddress(extAddr, addr)
+
+ // The stun server is probably stuffed, we've gone beyond min timeout, yet the address keeps changing.
+ minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second
+ if nextSleep < minSleep {
+ l.Debugf("%s keepalive aborting, sleep below min: %s < %s", s, nextSleep, minSleep)
+ return true
+ }
+ }
+
+ // Adjust the keepalives to fire only nextSleep after last write.
+ lastWrite := s.writeTrackingPacketConn.getLastWrite()
+ minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second
+ if nextSleep < minSleep {
+ nextSleep = minSleep
+ }
+ tryLater:
+ sleepFor := nextSleep
+
+ timeUntilNextKeepalive := time.Until(lastWrite.Add(sleepFor))
+ if timeUntilNextKeepalive > 0 {
+ sleepFor = timeUntilNextKeepalive
+ }
+
+ l.Debugf("%s stun sleeping for %s", s, sleepFor)
+
+ select {
+ case <-time.After(sleepFor):
+ case <-s.stop:
+ l.Debugf("%s stopping, aborting stun", s)
+ return false
+ }
+
+ if s.cfg.Options().IsStunDisabled() {
+ // Disabled, give up
+ l.Debugf("%s disabled, aborting stun ", s)
+ return false
+ }
+
+ // Check if any writes happened while we were sleeping, if they did, sleep again
+ lastWrite = s.writeTrackingPacketConn.getLastWrite()
+ if gap := time.Since(lastWrite); gap < nextSleep {
+ l.Debugf("%s stun last write gap less than next sleep: %s < %s. Will try later", s, gap, nextSleep)
+ goto tryLater
+ }
+
+ l.Debugf("%s stun keepalive", s)
+
+ extAddr, err = s.client.Keepalive()
+ if err != nil {
+ l.Debugf("%s stun keepalive on %s: %s (%v)", s, addr, err, extAddr)
+ return true
+ }
+ }
+}
+
+func (s *Service) setNATType(natType NATType) {
+ if natType != s.natType {
+ l.Debugf("Notifying %s of NAT type change: %s", s.subscriber, natType)
+ s.subscriber.OnNATTypeChanged(natType)
+ }
+ s.natType = natType
+}
+
+func (s *Service) setExternalAddress(addr *Host, via string) {
+ if areDifferent(s.addr, addr) {
+ l.Debugf("Notifying %s of address change: %s via %s", s.subscriber, addr, via)
+ s.subscriber.OnExternalAddressChanged(addr, via)
+ }
+ s.addr = addr
+}
+
+func (s *Service) String() string {
+ return s.name
+}
+
+func (s *Service) isCurrentNATTypePunchable() bool {
+ return s.natType == NATNone || s.natType == NATPortRestricted || s.natType == NATRestricted || s.natType == NATFull
+}
+
+func areDifferent(first, second *Host) bool {
+ if (first == nil) != (second == nil) {
+ return true
+ }
+ if first != nil {
+ return first.TransportAddr() != second.TransportAddr()
+ }
+ return false
+}
diff --git a/lib/util/utils.go b/lib/util/utils.go
index 15fb6db5..c2d9296f 100644
--- a/lib/util/utils.go
+++ b/lib/util/utils.go
@@ -10,7 +10,6 @@ import (
"fmt"
"net/url"
"reflect"
- "sort"
"strconv"
"strings"
)
@@ -111,21 +110,23 @@ func CopyMatchingTag(from interface{}, to interface{}, tag string, shouldCopy fu
}
}
-// UniqueStrings returns a list on unique strings, trimming and sorting them
-// at the same time.
-func UniqueStrings(ss []string) []string {
- var m = make(map[string]bool, len(ss))
- for _, s := range ss {
- m[strings.Trim(s, " ")] = true
+// UniqueTrimmedStrings returns a list on unique strings, trimming at the same time.
+func UniqueTrimmedStrings(ss []string) []string {
+ // Trim all first
+ for i, v := range ss {
+ ss[i] = strings.Trim(v, " ")
}
- var us = make([]string, 0, len(m))
- for k := range m {
- us = append(us, k)
+ var m = make(map[string]struct{}, len(ss))
+ var us = make([]string, 0, len(ss))
+ for _, v := range ss {
+ if _, ok := m[v]; ok {
+ continue
+ }
+ m[v] = struct{}{}
+ us = append(us, v)
}
- sort.Strings(us)
-
return us
}
diff --git a/lib/util/utils_test.go b/lib/util/utils_test.go
index 837d45bf..268c2786 100644
--- a/lib/util/utils_test.go
+++ b/lib/util/utils_test.go
@@ -74,10 +74,6 @@ func TestUniqueStrings(t *testing.T) {
nil,
nil,
},
- {
- []string{"b", "a"},
- []string{"a", "b"},
- },
{
[]string{" a ", " a ", "b ", " b"},
[]string{"a", "b"},
@@ -85,7 +81,7 @@ func TestUniqueStrings(t *testing.T) {
}
for _, test := range tests {
- result := UniqueStrings(test.input)
+ result := UniqueTrimmedStrings(test.input)
if len(result) != len(test.expected) {
t.Errorf("%s != %s", result, test.expected)
}
diff --git a/lib/versioner/simple.go b/lib/versioner/simple.go
index 43f55ce0..cd9ef97f 100644
--- a/lib/versioner/simple.go
+++ b/lib/versioner/simple.go
@@ -8,6 +8,7 @@ package versioner
import (
"path/filepath"
+ "sort"
"strconv"
"time"
@@ -71,7 +72,8 @@ func (v Simple) Archive(filePath string) error {
// Use all the found filenames. "~" sorts after "." so all old pattern
// files will be deleted before any new, which is as it should be.
- versions := util.UniqueStrings(append(oldVersions, newVersions...))
+ versions := util.UniqueTrimmedStrings(append(oldVersions, newVersions...))
+ sort.Strings(versions)
if len(versions) > v.keep {
for _, toRemove := range versions[:len(versions)-v.keep] {
diff --git a/lib/versioner/staggered.go b/lib/versioner/staggered.go
index 62495972..e9ac308e 100644
--- a/lib/versioner/staggered.go
+++ b/lib/versioner/staggered.go
@@ -8,6 +8,7 @@ package versioner
import (
"path/filepath"
+ "sort"
"strconv"
"time"
@@ -242,7 +243,9 @@ func (v *Staggered) Archive(filePath string) error {
// Use all the found filenames.
versions := append(oldVersions, newVersions...)
- v.expire(util.UniqueStrings(versions))
+ versions = util.UniqueTrimmedStrings(versions)
+ sort.Strings(versions)
+ v.expire(versions)
return nil
}