diff --git a/.github/workflows/build-infra-dockers.yaml b/.github/workflows/build-infra-dockers.yaml index 2295fedb1..cc507fe33 100644 --- a/.github/workflows/build-infra-dockers.yaml +++ b/.github/workflows/build-infra-dockers.yaml @@ -6,7 +6,7 @@ on: - infrastructure env: - GO_VERSION: "^1.20.5" + GO_VERSION: "^1.21.0" CGO_ENABLED: "0" BUILD_USER: docker BUILD_HOST: github.syncthing.net diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index 35ed2827c..c198c38dc 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -12,7 +12,7 @@ env: # The go version to use for builds. We set check-latest to true when # installing, so we get the latest patch version that matches the # expression. - GO_VERSION: "~1.20.7" + GO_VERSION: "~1.21.0" # Optimize compatibility on the slow archictures. GO386: softfloat @@ -48,7 +48,7 @@ jobs: runner: ["windows-latest", "ubuntu-latest", "macos-latest"] # The oldest version in this list should match what we have in our go.mod. # Variables don't seem to be supported here, or we could have done something nice. - go: ["1.19", "1.20"] + go: ["1.20", "1.21"] runs-on: ${{ matrix.runner }} steps: - name: Set git to use LF diff --git a/go.mod b/go.mod index 62e120623..006573b36 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,8 @@ module github.com/syncthing/syncthing -go 1.19 +go 1.20 require ( - github.com/AudriusButkevicius/pfilter v0.0.11 github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f github.com/alecthomas/kong v0.8.0 github.com/calmh/incontainer v0.0.0-20221224152218-b3e71b103d7a @@ -39,7 +38,7 @@ require ( github.com/prometheus/client_golang v1.16.0 github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.0 // indirect - github.com/quic-go/quic-go v0.34.0 + github.com/quic-go/quic-go v0.37.2-0.20230819081917-fe3c4f271df1 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 github.com/sasha-s/go-deadlock v0.3.1 github.com/shirou/gopsutil/v3 v3.23.6 @@ -48,13 +47,14 @@ require ( github.com/thejerf/suture/v4 v4.0.2 github.com/urfave/cli v1.22.14 github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 - golang.org/x/crypto v0.11.0 + golang.org/x/crypto v0.12.0 + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.12.0 - golang.org/x/sys v0.10.0 - golang.org/x/text v0.11.0 + golang.org/x/net v0.14.0 + golang.org/x/sys v0.11.0 + golang.org/x/text v0.12.0 golang.org/x/time v0.3.0 - golang.org/x/tools v0.11.0 + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 google.golang.org/protobuf v1.31.0 ) @@ -65,18 +65,16 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect + github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/oschwald/maxminddb-golang v1.11.0 // indirect github.com/petermattis/goid v0.0.0-20230518223814-80aa455d8761 // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect github.com/prometheus/client_model v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.3.2 // indirect - github.com/quic-go/qtls-go1-20 v0.2.2 // indirect + github.com/quic-go/qtls-go1-20 v0.3.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect - golang.org/x/exp v0.0.0-20230711023510-fffb14384f22 // indirect ) // https://github.com/gobwas/glob/pull/55 diff --git a/go.sum b/go.sum index 73d660e72..b359efa46 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/AudriusButkevicius/pfilter v0.0.11 h1:6emuvqNeH1gGlqkML35pEizyPcaxdAN4JO9sdgwcx78= -github.com/AudriusButkevicius/pfilter v0.0.11/go.mod h1:4eF1UYuEhoycTlr9IOP1sb0lL9u4nfAIouRqt2xJbzM= github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f h1:GmH5lT+moM7PbAJFBq57nH9WJ+wRnBXr/tyaYWbSAx8= github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f/go.mod h1:Nhfib1j/VFnLrXL9cHgA+/n2O6P5THuWelOnbfPNd78= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= @@ -81,8 +79,8 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8 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/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= +github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/greatroar/blobloom v0.7.2 h1:F30MGLHOcb4zr0pwCPTcKdlTM70rEgkf+LzdUPc5ss8= github.com/greatroar/blobloom v0.7.2/go.mod h1:mjMJ1hh1wjGVfr93QIHJ6FfDNVrA0IELv8OvMHJxHKs= github.com/hashicorp/golang-lru/v2 v2.0.4 h1:7GHuZcgid37q8o5i3QI9KMT4nCWQQ3Kx3Ov6bb9MfK0= @@ -159,12 +157,10 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk= github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= -github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= -github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= -github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.34.0 h1:OvOJ9LFjTySgwOTYUZmNoq0FzVicP8YujpV0kB7m2lU= -github.com/quic-go/quic-go v0.34.0/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g= +github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI= +github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.37.2-0.20230819081917-fe3c4f271df1 h1:SipxgcO/6iOMI9xVRY0zRQWClQuOVms7JVRQyqbdqiE= +github.com/quic-go/quic-go v0.37.2-0.20230819081917-fe3c4f271df1/go.mod h1:MPCuRq7KBK2hNcfKj/1iD1BGuN3eAYMeNxp3T42LRUg= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -210,10 +206,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/exp v0.0.0-20230711023510-fffb14384f22 h1:FqrVOBQxQ8r/UwwXibI0KMolVhvFiGobSfdE33deHJM= -golang.org/x/exp v0.0.0-20230711023510-fffb14384f22/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -235,8 +231,8 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug 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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -275,8 +271,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -287,8 +283,8 @@ 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -299,8 +295,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f 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/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= -golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 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= diff --git a/lib/connections/quic_dial.go b/lib/connections/quic_dial.go index 6c9a03670..7466f8ca3 100644 --- a/lib/connections/quic_dial.go +++ b/lib/connections/quic_dial.go @@ -52,27 +52,23 @@ func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL return internalConn{}, err } - var conn net.PacketConn - // We need to track who created the conn. - // Given we always pass the connection to quic, it assumes it's a remote connection it never closes it, - // So our wrapper around it needs to close it, but it only needs to close it if it's not the listening connection. + // If we created the conn we need to close it at the end. If we got a + // Transport from the registry we have no conn to close. var createdConn net.PacketConn - listenConn := d.registry.Get(uri.Scheme, packetConnUnspecified) - if listenConn != nil { - conn = listenConn.(net.PacketConn) - } else { + transport, _ := d.registry.Get(uri.Scheme, transportConnUnspecified).(*quic.Transport) + if transport == nil { if packetConn, err := net.ListenPacket("udp", ":0"); err != nil { return internalConn{}, err } else { - conn = packetConn createdConn = packetConn + transport = &quic.Transport{Conn: packetConn} } } ctx, cancel := context.WithTimeout(ctx, quicOperationTimeout) defer cancel() - session, err := quic.DialContext(ctx, conn, addr, uri.Host, d.tlsCfg, quicConfig) + session, err := transport.Dial(ctx, addr, d.tlsCfg, quicConfig) if err != nil { if createdConn != nil { _ = createdConn.Close() diff --git a/lib/connections/quic_listen.go b/lib/connections/quic_listen.go index 9eab87026..930376777 100644 --- a/lib/connections/quic_listen.go +++ b/lib/connections/quic_listen.go @@ -95,17 +95,22 @@ func (t *quicListener) serve(ctx context.Context) error { l.Infoln("Listen (BEP/quic):", err) return err } - defer func() { _ = udpConn.Close() }() + defer udpConn.Close() - svc, conn := stun.New(t.cfg, t, udpConn) - defer conn.Close() + tracer := &writeTrackingTracer{} + quicTransport := &quic.Transport{ + Conn: udpConn, + Tracer: tracer, + } + defer quicTransport.Close() + svc := stun.New(t.cfg, t, &transportPacketConn{tran: quicTransport}, tracer) go svc.Serve(ctx) - t.registry.Register(t.uri.Scheme, conn) - defer t.registry.Unregister(t.uri.Scheme, conn) + t.registry.Register(t.uri.Scheme, quicTransport) + defer t.registry.Unregister(t.uri.Scheme, quicTransport) - listener, err := quic.Listen(conn, t.tlsCfg, quicConfig) + listener, err := quicTransport.Listen(t.tlsCfg, quicConfig) if err != nil { l.Infoln("Listen (BEP/quic):", err) return err diff --git a/lib/connections/quic_misc.go b/lib/connections/quic_misc.go index d5f0b0dfd..238e48210 100644 --- a/lib/connections/quic_misc.go +++ b/lib/connections/quic_misc.go @@ -10,20 +10,22 @@ package connections import ( + "context" "crypto/tls" "net" "net/url" + "sync/atomic" "time" "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/logging" "github.com/syncthing/syncthing/lib/osutil" ) var quicConfig = &quic.Config{ - ConnectionIDLength: 4, - MaxIdleTimeout: 30 * time.Second, - KeepAlivePeriod: 15 * time.Second, + MaxIdleTimeout: 30 * time.Second, + KeepAlivePeriod: 15 * time.Second, } func quicNetwork(uri *url.URL) string { @@ -61,11 +63,75 @@ func (q *quicTlsConn) Close() error { } func (q *quicTlsConn) ConnectionState() tls.ConnectionState { - return q.Connection.ConnectionState().TLS.ConnectionState + return q.Connection.ConnectionState().TLS } -func packetConnUnspecified(conn interface{}) bool { - addr := conn.(net.PacketConn).LocalAddr() +func transportConnUnspecified(conn any) bool { + tran, ok := conn.(*quic.Transport) + if !ok { + return false + } + addr := tran.Conn.LocalAddr() ip, err := osutil.IPFromAddr(addr) return err == nil && ip.IsUnspecified() } + +type writeTrackingTracer struct { + lastWrite atomic.Int64 // unix nanos +} + +func (t *writeTrackingTracer) SentPacket(net.Addr, *logging.Header, logging.ByteCount, []logging.Frame) { + t.lastWrite.Store(time.Now().UnixNano()) +} + +func (t *writeTrackingTracer) SentVersionNegotiationPacket(_ net.Addr, dest, src logging.ArbitraryLenConnectionID, _ []quic.VersionNumber) { + t.lastWrite.Store(time.Now().UnixNano()) +} + +func (t *writeTrackingTracer) DroppedPacket(net.Addr, logging.PacketType, logging.ByteCount, logging.PacketDropReason) { +} + +func (t *writeTrackingTracer) LastWrite() time.Time { + return time.Unix(0, t.lastWrite.Load()) +} + +// A transportPacketConn is a net.PacketConn that uses a quic.Transport. +type transportPacketConn struct { + tran *quic.Transport + readDeadline atomic.Value // time.Time +} + +func (t *transportPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + ctx := context.Background() + if deadline, ok := t.readDeadline.Load().(time.Time); ok && !deadline.IsZero() { + var cancel context.CancelFunc + ctx, cancel = context.WithDeadline(ctx, deadline) + defer cancel() + } + return t.tran.ReadNonQUICPacket(ctx, p) +} + +func (t *transportPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + return t.tran.WriteTo(p, addr) +} + +func (t *transportPacketConn) Close() error { + return errUnsupported +} + +func (t *transportPacketConn) LocalAddr() net.Addr { + return t.tran.Conn.LocalAddr() +} + +func (t *transportPacketConn) SetDeadline(deadline time.Time) error { + return t.SetReadDeadline(deadline) +} + +func (t *transportPacketConn) SetReadDeadline(deadline time.Time) error { + t.readDeadline.Store(deadline) + return nil +} + +func (t *transportPacketConn) SetWriteDeadline(_ time.Time) error { + return nil // yolo +} diff --git a/lib/stun/stun.go b/lib/stun/stun.go index db96502cd..191b39c5b 100644 --- a/lib/stun/stun.go +++ b/lib/stun/stun.go @@ -9,10 +9,8 @@ package stun import ( "context" "net" - "sync/atomic" "time" - "github.com/AudriusButkevicius/pfilter" "github.com/ccding/go-stun/stun" "github.com/syncthing/syncthing/lib/config" @@ -21,8 +19,10 @@ import ( const stunRetryInterval = 5 * time.Minute -type Host = stun.Host -type NATType = stun.NATType +type ( + Host = stun.Host + NATType = stun.NATType +) // NAT types. @@ -38,38 +38,6 @@ const ( NATSymmetricUDPFirewall = stun.NATSymmetricUDPFirewall ) -type writeTrackingUdpConn struct { - // Needs to be UDPConn not PacketConn, as pfilter checks for WriteMsgUDP/ReadMsgUDP - // and even if we embed UDPConn here, in place of a PacketConn, seems the interface - // check fails. - *net.UDPConn - lastWrite atomic.Int64 -} - -func (c *writeTrackingUdpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { - c.lastWrite.Store(time.Now().Unix()) - return c.UDPConn.WriteTo(p, addr) -} - -func (c *writeTrackingUdpConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) { - c.lastWrite.Store(time.Now().Unix()) - return c.UDPConn.WriteMsgUDP(b, oob, addr) -} - -func (c *writeTrackingUdpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) { - c.lastWrite.Store(time.Now().Unix()) - return c.UDPConn.WriteToUDP(b, addr) -} - -func (c *writeTrackingUdpConn) Write(b []byte) (int, error) { - c.lastWrite.Store(time.Now().Unix()) - return c.UDPConn.Write(b) -} - -func (c *writeTrackingUdpConn) getLastWrite() time.Time { - return time.Unix(c.lastWrite.Load(), 0) -} - type Subscriber interface { OnNATTypeChanged(natType NATType) OnExternalAddressChanged(address *Host, via string) @@ -79,30 +47,21 @@ type Service struct { name string cfg config.Wrapper subscriber Subscriber - stunConn net.PacketConn client *stun.Client - writeTrackingUdpConn *writeTrackingUdpConn + lastWriter LastWriter natType NATType addr *Host } -func New(cfg config.Wrapper, subscriber Subscriber, conn *net.UDPConn) (*Service, net.PacketConn) { - // Wrap the original connection to track writes on it - writeTrackingUdpConn := &writeTrackingUdpConn{UDPConn: 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(writeTrackingUdpConn) - otherDataConn := filterConn.NewConn(otherDataPriority, nil) - stunConn := filterConn.NewConn(stunFilterPriority, &stunFilter{ - ids: make(map[string]time.Time), - }) - - filterConn.Start() +type LastWriter interface { + LastWrite() time.Time +} +func New(cfg config.Wrapper, subscriber Subscriber, conn net.PacketConn, lastWriter LastWriter) *Service { // Construct the client to use the stun conn - client := stun.NewClientWithConnection(stunConn) + client := stun.NewClientWithConnection(conn) client.SetSoftwareName("") // Explicitly unset this, seems to freak some servers out. // Return the service and the other conn to the client @@ -117,15 +76,14 @@ func New(cfg config.Wrapper, subscriber Subscriber, conn *net.UDPConn) (*Service cfg: cfg, subscriber: subscriber, - stunConn: stunConn, client: client, - writeTrackingUdpConn: writeTrackingUdpConn, + lastWriter: lastWriter, natType: NATUnknown, addr: nil, } - return s, otherDataConn + return s } func (s *Service) Serve(ctx context.Context) error { @@ -134,13 +92,6 @@ func (s *Service) Serve(ctx context.Context) error { s.setExternalAddress(nil, "") }() - // Closing s.stunConn unblocks operations that use the connection - // (Discover, Keepalive) and might otherwise block us from returning. - go func() { - <-ctx.Done() - _ = s.stunConn.Close() - }() - timer := time.NewTimer(time.Millisecond) for { @@ -244,6 +195,7 @@ func (s *Service) stunKeepAlive(ctx context.Context, addr string, extAddr *Host) l.Debugf("%s starting stun keepalive via %s, next sleep %s", s, addr, nextSleep) + var ourLastWrite time.Time for { if areDifferent(s.addr, extAddr) { // If the port has changed (addresses are not equal but the hosts are equal), @@ -264,7 +216,10 @@ func (s *Service) stunKeepAlive(ctx context.Context, addr string, extAddr *Host) } // Adjust the keepalives to fire only nextSleep after last write. - lastWrite := s.writeTrackingUdpConn.getLastWrite() + lastWrite := ourLastWrite + if quicLastWrite := s.lastWriter.LastWrite(); quicLastWrite.After(lastWrite) { + lastWrite = quicLastWrite + } minSleep := time.Duration(s.cfg.Options().StunKeepaliveMinS) * time.Second if nextSleep < minSleep { nextSleep = minSleep @@ -293,7 +248,7 @@ func (s *Service) stunKeepAlive(ctx context.Context, addr string, extAddr *Host) } // Check if any writes happened while we were sleeping, if they did, sleep again - lastWrite = s.writeTrackingUdpConn.getLastWrite() + lastWrite = s.lastWriter.LastWrite() 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 @@ -306,6 +261,7 @@ func (s *Service) stunKeepAlive(ctx context.Context, addr string, extAddr *Host) l.Debugf("%s stun keepalive on %s: %s (%v)", s, addr, err, extAddr) return } + ourLastWrite = time.Now() } }