From 302a01e28a3c05df27945012cbecc6a1daa91a13 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 6 Feb 2024 16:40:31 -0500 Subject: [PATCH 01/17] fix: cancel all outgoing requests when disconnect to prevent ANR --- Android/app/src/go/intra/doh/doh_test.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Android/app/src/go/intra/doh/doh_test.go b/Android/app/src/go/intra/doh/doh_test.go index 7ad0797a..05fc0aeb 100644 --- a/Android/app/src/go/intra/doh/doh_test.go +++ b/Android/app/src/go/intra/doh/doh_test.go @@ -19,7 +19,6 @@ import ( "encoding/binary" "errors" "io" - "io/ioutil" "net" "net/http" "net/http/httptrace" @@ -27,6 +26,7 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/require" "golang.org/x/net/dns/dnsmessage" ) @@ -247,7 +247,7 @@ func TestRequest(t *testing.T) { if req.URL.String() != testURL { t.Errorf("URL mismatch: %s != %s", req.URL.String(), testURL) } - reqBody, err := ioutil.ReadAll(req.Body) + reqBody, err := io.ReadAll(req.Body) if err != nil { t.Error(err) } @@ -889,3 +889,9 @@ func TestServfail(t *testing.T) { t.Errorf("Wrong question: %v", servfail.Questions[0]) } } + +func TestQueryCanBeCancelled(t *testing.T) { + server, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)}) + require.NoError(t, err) + defer server.Close() +} From 4549e87b1c33ceb4a73b68f53b69c96bc9a49e39 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 6 Feb 2024 16:41:52 -0500 Subject: [PATCH 02/17] update go mod --- go.mod | 5 +++++ go.sum | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/go.mod b/go.mod index d9b8bd37..6fb7b038 100644 --- a/go.mod +++ b/go.mod @@ -7,14 +7,19 @@ require ( github.com/Jigsaw-Code/getsni v1.0.0 github.com/Jigsaw-Code/outline-sdk v0.0.7 github.com/eycorsican/go-tun2socks v1.16.11 + github.com/stretchr/testify v1.8.2 golang.org/x/mobile v0.0.0-20231006135142-2b44d11868fe golang.org/x/net v0.16.0 golang.org/x/sys v0.13.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/mod v0.13.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/tools v0.14.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 166b2e17..9af645de 100644 --- a/go.sum +++ b/go.sum @@ -4,13 +4,24 @@ github.com/Jigsaw-Code/getsni v1.0.0 h1:OUTIu7wTBi/7DMX+RkZrN7XhU3UDevTEsAWK4gsq github.com/Jigsaw-Code/getsni v1.0.0/go.mod h1:Ps0Ec3fVMKLyAItVbMKoQFq1lDjtFQXZ+G5nRNNh/QE= github.com/Jigsaw-Code/outline-sdk v0.0.7 h1:WlFaV1tFpIQ/pflrKwrQuNIP3kJpgh7yJuqiTb54sGA= github.com/Jigsaw-Code/outline-sdk v0.0.7/go.mod h1:hhlKz0+r9wSDFT8usvN8Zv/BFToCIFAUn1P2Qk8G2CM= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/eycorsican/go-tun2socks v1.16.11 h1:+hJDNgisrYaGEqoSxhdikMgMJ4Ilfwm/IZDrWRrbaH8= github.com/eycorsican/go-tun2socks v1.16.11/go.mod h1:wgB2BFT8ZaPKyKOQ/5dljMG/YIow+AIXyq4KBwJ5sGQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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.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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -35,5 +46,9 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From e11c59a62df88dab9b872bd38f85a7900002efaa Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 14 Feb 2024 18:40:38 -0500 Subject: [PATCH 03/17] add logging infra to go backend --- .gitignore | 1 + .../app/src/go/internal/logging/logging.go | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 Android/app/src/go/internal/logging/logging.go diff --git a/.gitignore b/.gitignore index 418b3b88..40c8400d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.iml *.p12 .gradle +.vscode/ /Android/local.properties /Android/keystore.properties /Android/.idea/ diff --git a/Android/app/src/go/internal/logging/logging.go b/Android/app/src/go/internal/logging/logging.go new file mode 100644 index 00000000..0a6cd806 --- /dev/null +++ b/Android/app/src/go/internal/logging/logging.go @@ -0,0 +1,26 @@ +// Copyright 2024 Jigsaw Operations LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logging + +import ( + "io" + "log" + "os" +) + +var Debug = log.New(io.Discard, "[DEBUG] ", log.LstdFlags) +var Info = log.New(os.Stdout, "[INFO] ", log.LstdFlags) +var Warn = log.New(os.Stderr, "[WARN] ", log.LstdFlags) +var Err = log.New(os.Stderr, "[ERROR] ", log.LstdFlags) From f2a9d9d133d4f9fcb9669578ac7a8a8d21579b62 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 20 Feb 2024 18:56:49 -0500 Subject: [PATCH 04/17] add backend package containing DoHServer and Intra Session --- Android/app/build.gradle | 5 +- Android/app/src/go/backend/doc.go | 34 ++++ Android/app/src/go/backend/doh.go | 152 +++++++++++++++++ .../src/go/{intra/android => backend}/init.go | 7 +- Android/app/src/go/backend/tunnel.go | 92 +++++++++++ .../app/src/go/{intra => }/doh/client_auth.go | 12 +- .../go/{intra => }/doh/client_auth_test.go | 0 Android/app/src/go/{intra => }/doh/doh.go | 122 +++++++------- .../app/src/go/{intra => }/doh/doh_test.go | 154 +++++++++++------- .../app/src/go/{intra => }/doh/ipmap/ipmap.go | 4 +- .../go/{intra => }/doh/ipmap/ipmap_test.go | 0 Android/app/src/go/{intra => }/doh/padding.go | 0 Android/app/src/go/intra/android/tun2socks.go | 110 ------------- Android/app/src/go/intra/doh/atomic.go | 38 ----- .../app/src/go/intra/split/example/main.go | 2 +- .../src/go/{internal => }/logging/logging.go | 2 +- .../src/go/{intra/android => tuntap}/tun.go | 6 +- go.mod | 4 +- 18 files changed, 458 insertions(+), 286 deletions(-) create mode 100644 Android/app/src/go/backend/doc.go create mode 100644 Android/app/src/go/backend/doh.go rename Android/app/src/go/{intra/android => backend}/init.go (84%) create mode 100644 Android/app/src/go/backend/tunnel.go rename Android/app/src/go/{intra => }/doh/client_auth.go (89%) rename Android/app/src/go/{intra => }/doh/client_auth_test.go (100%) rename Android/app/src/go/{intra => }/doh/doh.go (78%) rename Android/app/src/go/{intra => }/doh/doh_test.go (86%) rename Android/app/src/go/{intra => }/doh/ipmap/ipmap.go (97%) rename Android/app/src/go/{intra => }/doh/ipmap/ipmap_test.go (100%) rename Android/app/src/go/{intra => }/doh/padding.go (100%) delete mode 100644 Android/app/src/go/intra/android/tun2socks.go delete mode 100644 Android/app/src/go/intra/doh/atomic.go rename Android/app/src/go/{internal => }/logging/logging.go (93%) rename Android/app/src/go/{intra/android => tuntap}/tun.go (90%) diff --git a/Android/app/build.gradle b/Android/app/build.gradle index 0074f7f8..fccb012e 100644 --- a/Android/app/build.gradle +++ b/Android/app/build.gradle @@ -17,9 +17,8 @@ try { // Go backend build constants def goSourceDir = "${projectDir}/src/go" -def goSourcePackages = ["${goSourceDir}/intra", - "${goSourceDir}/intra/android", - "${goSourceDir}/intra/doh", +def goSourcePackages = ["${goSourceDir}/backend", + "${goSourceDir}/intra", "${goSourceDir}/intra/split", "${goSourceDir}/intra/protect"] def goBuildDir = file("${buildDir}/go") diff --git a/Android/app/src/go/backend/doc.go b/Android/app/src/go/backend/doc.go new file mode 100644 index 00000000..93d17d7f --- /dev/null +++ b/Android/app/src/go/backend/doc.go @@ -0,0 +1,34 @@ +// Copyright 2024 Jigsaw Operations LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* +Package backend exposes objects and functions for Intra's application code (i.e., Java code). +It is the only packages that should be used by the application code, and is not intended for +use by other Go code. + +This package provides the following features: + +# DoHServer + +[DoHServer] connects to a DNS-over-HTTPS (DoH) server that handles DNS requests. + +# Session + +Intra [Session] reads from a local tun device and: + +- redirects DNS requests to a specific [DoHServer] +- splits TLS packets into two randomly sized packets +- Forwards all other traffic untouched +*/ +package backend diff --git a/Android/app/src/go/backend/doh.go b/Android/app/src/go/backend/doh.go new file mode 100644 index 00000000..83a9f46a --- /dev/null +++ b/Android/app/src/go/backend/doh.go @@ -0,0 +1,152 @@ +// Copyright 2024 Jigsaw Operations LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "context" + "errors" + "fmt" + "strings" + + "localhost/Intra/Android/app/src/go/doh" + "localhost/Intra/Android/app/src/go/intra/protect" +) + +// DoHServer represents a DNS-over-HTTPS server. +type DoHServer struct { + tspt doh.Transport +} + +// NewDoHServer creates a DoHServer that connects to the specified DoH server. +// +// url is the URL of a DoH server (no template, POST-only). +// +// ipsStr is an optional comma-separated list of IP addresses for the server. It will be used when the url +// cannot be resolved to working IP addresses. (string is required cuz gomobile doesn't support []string) +// +// protector is Android's socket protector to use for all external network activity. +// +// listener will be notified after each DNS query succeeds or fails. +func NewDoHServer( + url string, ipsStr string, protector protect.Protector, listener DoHListener, +) (*DoHServer, error) { + ips := []string{} + if len(ipsStr) > 0 { + ips = strings.Split(ipsStr, ",") + } + dialer := protect.MakeDialer(protector) + t, err := doh.NewTransport(url, ips, dialer, nil, makeInternalDoHListener(listener)) + if err != nil { + return nil, err + } + return &DoHServer{t}, nil +} + +// dohQuery is used by [DoHServer].Probe. +var dohQuery = []byte{ + 0, 0, // [0-1] query ID + 1, 0, // [2-3] flags, RD=1 + 0, 1, // [4-5] QDCOUNT (number of queries) = 1 + 0, 0, // [6-7] ANCOUNT (number of answers) = 0 + 0, 0, // [8-9] NSCOUNT (number of authoritative answers) = 0 + 0, 0, // [10-11] ARCOUNT (number of additional records) = 0 + + // Start of first query + 7, 'y', 'o', 'u', 't', 'u', 'b', 'e', + 3, 'c', 'o', 'm', + 0, // null terminator of FQDN (DNS root) + 0, 1, // QTYPE = A + 0, 1, // QCLASS = IN (Internet) +} + +// Probe checks if this server can handle DNS-over-HTTPS (DoH) requests. +// +// If the server responds correctly, the function returns nil. Otherwise, the function returns an error. +func (s *DoHServer) Probe() error { + resp, err := s.tspt.Query(context.Background(), dohQuery) + if err != nil { + return fmt.Errorf("failed to send query: %w", err) + } + if len(resp) == 0 { + return errors.New("invalid DoH response") + } + return nil +} + +////////// event listeners + +// DoHQueryToken is an opaque object used to match responses to queries. +// The same DoHQueryToken that returned by [DoHListener].OnQuery be passed +// to the corresponding [DoHListener].OnResponse. +type DoHQueryToken doh.Token + +// DoHListener is an event listener that receives DoH request reports. +// Application code can implement this interface to receive these reports. +type DoHListener interface { + // OnQuery will be called when a DoH request is issued to url. + // Application code return an arbitrary DoHQueryToken object for internal use, + // the same object will be passed to OnResponse. + OnQuery(url string) DoHQueryToken + + // OnResponse will be called when a DoH response has been received. + OnResponse(DoHQueryToken, *DoHQueryStats) +} + +// DoHStatus is an integer representing the status of a DoH transaction. +type DoHStatus = int + +const ( + DoHStatusComplete DoHStatus = doh.Complete // Transaction completed successfully + DoHStatusSendFailed DoHStatus = doh.SendFailed // Failed to send query + DoHStatusHTTPError DoHStatus = doh.HTTPError // Got a non-200 HTTP status + DoHStatusBadQuery DoHStatus = doh.BadQuery // Malformed input + DoHStatusBadResponse DoHStatus = doh.BadResponse // Response was invalid + DoHStatusInternalError DoHStatus = doh.InternalError // This should never happen +) + +// DoHQueryStats is the summary of a DNS transaction. +// It will be reported to [DoHListener].OnResponse when it is complete. +type DoHQueryStats struct { + summ *doh.Summary +} + +func (q DoHQueryStats) GetQuery() []byte { return q.summ.Query } +func (q DoHQueryStats) GetResponse() []byte { return q.summ.Response } +func (q DoHQueryStats) GetServer() string { return q.summ.Server } +func (q DoHQueryStats) GetStatus() DoHStatus { return q.summ.Status } +func (q DoHQueryStats) GetHTTPStatus() int { return q.summ.HTTPStatus } +func (q DoHQueryStats) GetLatency() float64 { return q.summ.Latency } + +// dohListenerAdapter is an adapter for the internal [doh.Listener]. +type dohListenerAdapter struct { + l DoHListener +} + +// makeInternalDoHListener creates a [doh.Listener] from the public [DoHListener] +// interface that will be implemented by the application code. +func makeInternalDoHListener(l DoHListener) doh.Listener { + if l == nil { + return nil + } + return &dohListenerAdapter{l} +} + +func (e dohListenerAdapter) OnQuery(url string) doh.Token { + return e.l.OnQuery(url) +} + +func (e dohListenerAdapter) OnResponse(t doh.Token, s *doh.Summary) { + e.l.OnResponse(t, &DoHQueryStats{s}) +} diff --git a/Android/app/src/go/intra/android/init.go b/Android/app/src/go/backend/init.go similarity index 84% rename from Android/app/src/go/intra/android/init.go rename to Android/app/src/go/backend/init.go index a91226f8..fe9a89c8 100644 --- a/Android/app/src/go/intra/android/init.go +++ b/Android/app/src/go/backend/init.go @@ -1,4 +1,4 @@ -// Copyright 2023 Jigsaw Operations LLC +// Copyright 2024 Jigsaw Operations LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,16 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package tun2socks +package backend import ( "runtime/debug" - - "github.com/eycorsican/go-tun2socks/common/log" ) func init() { // Conserve memory by increasing garbage collection frequency. debug.SetGCPercent(10) - log.SetLevel(log.WARN) } diff --git a/Android/app/src/go/backend/tunnel.go b/Android/app/src/go/backend/tunnel.go new file mode 100644 index 00000000..f9814a2e --- /dev/null +++ b/Android/app/src/go/backend/tunnel.go @@ -0,0 +1,92 @@ +// Copyright 2024 Jigsaw Operations LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "errors" + "io" + "io/fs" + "localhost/Intra/Android/app/src/go/intra" + "localhost/Intra/Android/app/src/go/intra/protect" + "localhost/Intra/Android/app/src/go/tuntap" + "log" + "os" + + "github.com/Jigsaw-Code/outline-sdk/network" +) + +// Session represents an Intra communication session. +// +// TODO: copy methods of intra.Tunnel here and do not expose intra package +type Session struct { + // TODO: hide this internal Tunnel when finished moving everything to backend + *intra.Tunnel +} + +func (s *Session) SetDoHServer(svr *DoHServer) { s.SetDNS(svr.tspt) } + +// ConnectSession reads packets from a TUN device and applies the Intra routing +// rules. Currently, this only consists of redirecting DNS packets to a specified +// server; all other data flows directly to its destination. +// +// fd is the TUN device. The intra [Session] acquires an additional reference to it, +// which is released by [Session].Disconnect(), so the caller must close `fd` _and_ call +// Disconnect() in order to close the TUN device. +// +// fakedns is the DNS server that the system believes it is using, in "host:port" style. +// The port is normally 53. +// +// dohdns is the initial DoH transport and must not be nil. +// +// protector is a wrapper for Android's VpnService.protect() method. +// +// eventListener will be provided with a summary of each TCP and UDP socket when it is closed. +func ConnectSession( + fd int, fakedns string, dohdns *DoHServer, protector protect.Protector, listener intra.Listener, +) (*Session, error) { + // TODO: define Tunnel type in this backend package, and do not export intra package + tun, err := tuntap.MakeTunDeviceFromFD(fd) + if err != nil { + return nil, err + } + if dohdns == nil { + return nil, errors.New("dohdns must not be nil") + } + t, err := intra.NewTunnel(fakedns, dohdns.tspt, tun, protector, listener) + if err != nil { + return nil, err + } + go copyUntilEOF(t, tun) + go copyUntilEOF(tun, t) + return &Session{t}, nil +} + +func copyUntilEOF(dst, src io.ReadWriteCloser) { + log.Printf("[debug] start relaying traffic [%s] -> [%s]", src, dst) + defer log.Printf("[debug] stop relaying traffic [%s] -> [%s]", src, dst) + + const commonMTU = 1500 + buf := make([]byte, commonMTU) + for { + _, err := io.CopyBuffer(dst, src, buf) + if err == nil || isErrClosed(err) { + return + } + } +} + +func isErrClosed(err error) bool { + return errors.Is(err, os.ErrClosed) || errors.Is(err, fs.ErrClosed) || errors.Is(err, network.ErrClosed) +} diff --git a/Android/app/src/go/intra/doh/client_auth.go b/Android/app/src/go/doh/client_auth.go similarity index 89% rename from Android/app/src/go/intra/doh/client_auth.go rename to Android/app/src/go/doh/client_auth.go index e7e5aa97..c22f1936 100644 --- a/Android/app/src/go/intra/doh/client_auth.go +++ b/Android/app/src/go/doh/client_auth.go @@ -22,7 +22,7 @@ import ( "errors" "io" - "github.com/eycorsican/go-tun2socks/common/log" + "localhost/Intra/Android/app/src/go/logging" ) // ClientAuth interface for providing TLS certificates and signatures. @@ -52,12 +52,12 @@ type clientAuthWrapper struct { func (ca *clientAuthWrapper) GetClientCertificate( info *tls.CertificateRequestInfo) (*tls.Certificate, error) { if ca.signer == nil { - log.Warnf("Client certificate requested but not supported") + logging.Warn.Println("Client certificate requested but not supported") return &tls.Certificate{}, nil } cert := ca.signer.GetClientCertificate() if cert == nil { - log.Warnf("Unable to fetch client certificate") + logging.Warn.Println("Unable to fetch client certificate") return &tls.Certificate{}, nil } chain := [][]byte{cert} @@ -67,13 +67,13 @@ func (ca *clientAuthWrapper) GetClientCertificate( } leaf, err := x509.ParseCertificate(cert) if err != nil { - log.Warnf("Unable to parse client certificate: %v", err) + logging.Warn.Printf("Unable to parse client certificate: %v\n", err) return &tls.Certificate{}, nil } _, isECDSA := leaf.PublicKey.(*ecdsa.PublicKey) if !isECDSA { // RSA-PSS and RSA-SSA both need explicit signature generation support. - log.Warnf("Only ECDSA client certificates are supported") + logging.Warn.Println("Only ECDSA client certificates are supported") return &tls.Certificate{}, nil } return &tls.Certificate{ @@ -91,7 +91,7 @@ func (ca *clientAuthWrapper) Public() crypto.PublicKey { cert := ca.signer.GetClientCertificate() leaf, err := x509.ParseCertificate(cert) if err != nil { - log.Warnf("Unable to parse client certificate: %v", err) + logging.Warn.Printf("Unable to parse client certificate: %v\n", err) return nil } return leaf.PublicKey diff --git a/Android/app/src/go/intra/doh/client_auth_test.go b/Android/app/src/go/doh/client_auth_test.go similarity index 100% rename from Android/app/src/go/intra/doh/client_auth_test.go rename to Android/app/src/go/doh/client_auth_test.go diff --git a/Android/app/src/go/intra/doh/doh.go b/Android/app/src/go/doh/doh.go similarity index 78% rename from Android/app/src/go/intra/doh/doh.go rename to Android/app/src/go/doh/doh.go index 6242ef51..6f22eaf2 100644 --- a/Android/app/src/go/intra/doh/doh.go +++ b/Android/app/src/go/doh/doh.go @@ -16,12 +16,12 @@ package doh import ( "bytes" + "context" "crypto/tls" "encoding/binary" "errors" "fmt" "io" - "io/ioutil" "math" "net" "net/http" @@ -32,9 +32,10 @@ import ( "sync" "time" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/doh/ipmap" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/split" - "github.com/eycorsican/go-tun2socks/common/log" + "localhost/Intra/Android/app/src/go/doh/ipmap" + "localhost/Intra/Android/app/src/go/intra/split" + "localhost/Intra/Android/app/src/go/logging" + "golang.org/x/net/dns/dnsmessage" ) @@ -69,7 +70,7 @@ type Summary struct { } // A Token is an opaque handle used to match responses to queries. -type Token interface{} +type Token = interface{} // Listener receives Summaries. type Listener interface { @@ -77,13 +78,15 @@ type Listener interface { OnResponse(Token, *Summary) } -// Transport represents a DNS query transport. This interface is exported by gobind, -// so it has to be very simple. +// Transport represents a DNS query transport. type Transport interface { - // Given a DNS query (including ID), returns a DNS response with matching - // ID, or an error if no response was received. The error may be accompanied - // by a SERVFAIL response if appropriate. - Query(q []byte) ([]byte, error) + // Query sends a DNS query represented by q (including ID) to this DoH server + // (located at GetURL) using the provided context, and returns the correponding + // + // A non-nil error will be returned if no response was received from the DoH server, + // the error may also be accompanied by a SERVFAIL response if appropriate. + Query(ctx context.Context, q []byte) ([]byte, error) + // Return the server URL used to initialize this transport. GetURL() string } @@ -105,8 +108,8 @@ type transport struct { // Wait up to three seconds for the TCP handshake to complete. const tcpTimeout time.Duration = 3 * time.Second -func (t *transport) dial(network, addr string) (net.Conn, error) { - log.Debugf("Dialing %s", addr) +func (t *transport) dial(ctx context.Context, network, addr string) (net.Conn, error) { + logging.Debug.Printf("Dialing %s\n", addr) domain, portStr, err := net.SplitHostPort(addr) if err != nil { return nil, err @@ -125,23 +128,23 @@ func (t *transport) dial(network, addr string) (net.Conn, error) { ips := t.ips.Get(domain) confirmed := ips.Confirmed() if confirmed != nil { - log.Debugf("Trying confirmed IP %s for addr %s", confirmed.String(), addr) - if conn, err = split.DialWithSplitRetry(t.dialer, tcpaddr(confirmed), nil); err == nil { - log.Infof("Confirmed IP %s worked", confirmed.String()) + logging.Debug.Printf("Trying confirmed IP %s for addr %s\n", confirmed.String(), addr) + if conn, err = split.DialWithSplitRetry(ctx, t.dialer, tcpaddr(confirmed), nil); err == nil { + logging.Info.Printf("Confirmed IP %s worked\n", confirmed.String()) return conn, nil } - log.Debugf("Confirmed IP %s failed with err %v", confirmed.String(), err) + logging.Debug.Printf("Confirmed IP %s failed with err %v\n", confirmed.String(), err) ips.Disconfirm(confirmed) } - log.Debugf("Trying all IPs") + logging.Debug.Println("Trying all IPs") for _, ip := range ips.GetAll() { if ip.Equal(confirmed) { // Don't try this IP twice. continue } - if conn, err = split.DialWithSplitRetry(t.dialer, tcpaddr(ip), nil); err == nil { - log.Infof("Found working IP: %s", ip.String()) + if conn, err = split.DialWithSplitRetry(ctx, t.dialer, tcpaddr(ip), nil); err == nil { + logging.Info.Printf("Found working IP: %s\n", ip.String()) return conn, nil } } @@ -150,16 +153,17 @@ func (t *transport) dial(network, addr string) (net.Conn, error) { // NewTransport returns a DoH DNSTransport, ready for use. // This is a POST-only DoH implementation, so the DoH template should be a URL. +// // `rawurl` is the DoH template in string form. -// `addrs` is a list of domains or IP addresses to use as fallback, if the hostname // -// lookup fails or returns non-working addresses. +// `addrs` is a list of domains or IP addresses to use as fallback, if the hostname lookup fails or +// returns non-working addresses. // // `dialer` is the dialer that the transport will use. The transport will modify the dialer's -// -// timeout but will not mutate it otherwise. +// timeout but will not mutate it otherwise. // // `auth` will provide a client certificate if required by the TLS server. +// // `listener` will receive the status of each DNS query when it is complete. func NewTransport(rawurl string, addrs []string, dialer *net.Dialer, auth ClientAuth, listener Listener) (Transport, error) { if dialer == nil { @@ -211,7 +215,7 @@ func NewTransport(rawurl string, addrs []string, dialer *net.Dialer, auth Client // Override the dial function. t.client.Transport = &http.Transport{ - Dial: t.dial, + DialContext: t.dial, ForceAttemptHTTP2: true, TLSHandshakeTimeout: 10 * time.Second, ResponseHeaderTimeout: 20 * time.Second, // Same value as Android DNS-over-TLS @@ -247,7 +251,7 @@ func (e *httpError) Error() string { // Independent of the query's success or failure, this function also returns the // address of the server on a best-effort basis, or nil if the address could not // be determined. -func (t *transport) doQuery(q []byte) (response []byte, server *net.TCPAddr, qerr *queryError) { +func (t *transport) doQuery(ctx context.Context, q []byte) (response []byte, server *net.TCPAddr, qerr *queryError) { if len(q) < 2 { qerr = &queryError{BadQuery, fmt.Errorf("Query length is %d", len(q))} return @@ -272,7 +276,7 @@ func (t *transport) doQuery(q []byte) (response []byte, server *net.TCPAddr, qer // Zero out the query ID. id := binary.BigEndian.Uint16(q) binary.BigEndian.PutUint16(q, 0) - req, err := http.NewRequest(http.MethodPost, t.url, bytes.NewBuffer(q)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, t.url, bytes.NewBuffer(q)) if err != nil { qerr = &queryError{InternalError, err} return @@ -324,13 +328,13 @@ func (t *transport) sendRequest(id uint16, req *http.Request) (response []byte, if qerr == nil { return } - log.Infof("%d Query failed: %v", id, qerr) + logging.Info.Printf("%d Query failed: %v\n", id, qerr) if server != nil { - log.Debugf("%d Disconfirming %s", id, server.IP.String()) + logging.Debug.Printf("%d Disconfirming %s\n", id, server.IP.String()) t.ips.Get(hostname).Disconfirm(server.IP) } if conn != nil { - log.Infof("%d Closing failing DoH socket", id) + logging.Info.Printf("%d Closing failing DoH socket\n", id) conn.Close() } }() @@ -341,10 +345,10 @@ func (t *transport) sendRequest(id uint16, req *http.Request) (response []byte, // reading the variables it has set. trace := httptrace.ClientTrace{ GetConn: func(hostPort string) { - log.Debugf("%d GetConn(%s)", id, hostPort) + logging.Debug.Printf("%d GetConn(%s)\n", id, hostPort) }, GotConn: func(info httptrace.GotConnInfo) { - log.Debugf("%d GotConn(%v)", id, info) + logging.Debug.Printf("%d GotConn(%v)\n", id, info) if info.Conn == nil { return } @@ -353,41 +357,41 @@ func (t *transport) sendRequest(id uint16, req *http.Request) (response []byte, server = conn.RemoteAddr().(*net.TCPAddr) }, PutIdleConn: func(err error) { - log.Debugf("%d PutIdleConn(%v)", id, err) + logging.Debug.Printf("%d PutIdleConn(%v)\n", id, err) }, GotFirstResponseByte: func() { - log.Debugf("%d GotFirstResponseByte()", id) + logging.Debug.Printf("%d GotFirstResponseByte()\n", id) }, Got100Continue: func() { - log.Debugf("%d Got100Continue()", id) + logging.Debug.Printf("%d Got100Continue()\n", id) }, Got1xxResponse: func(code int, header textproto.MIMEHeader) error { - log.Debugf("%d Got1xxResponse(%d, %v)", id, code, header) + logging.Debug.Printf("%d Got1xxResponse(%d, %v)\n", id, code, header) return nil }, DNSStart: func(info httptrace.DNSStartInfo) { - log.Debugf("%d DNSStart(%v)", id, info) + logging.Debug.Printf("%d DNSStart(%v)\n", id, info) }, DNSDone: func(info httptrace.DNSDoneInfo) { - log.Debugf("%d, DNSDone(%v)", id, info) + logging.Debug.Printf("%d, DNSDone(%v)\n", id, info) }, ConnectStart: func(network, addr string) { - log.Debugf("%d ConnectStart(%s, %s)", id, network, addr) + logging.Debug.Printf("%d ConnectStart(%s, %s)\n", id, network, addr) }, ConnectDone: func(network, addr string, err error) { - log.Debugf("%d ConnectDone(%s, %s, %v)", id, network, addr, err) + logging.Debug.Printf("%d ConnectDone(%s, %s, %v)\n", id, network, addr, err) }, TLSHandshakeStart: func() { - log.Debugf("%d TLSHandshakeStart()", id) + logging.Debug.Printf("%d TLSHandshakeStart()\n", id) }, TLSHandshakeDone: func(state tls.ConnectionState, err error) { - log.Debugf("%d TLSHandshakeDone(%v, %v)", id, state, err) + logging.Debug.Printf("%d TLSHandshakeDone(%v, %v)\n", id, state, err) }, WroteHeaders: func() { - log.Debugf("%d WroteHeaders()", id) + logging.Debug.Printf("%d WroteHeaders()\n", id) }, WroteRequest: func(info httptrace.WroteRequestInfo) { - log.Debugf("%d WroteRequest(%v)", id, info) + logging.Debug.Printf("%d WroteRequest(%v)\n", id, info) }, } req = req.WithContext(httptrace.WithClientTrace(req.Context(), &trace)) @@ -396,20 +400,20 @@ func (t *transport) sendRequest(id uint16, req *http.Request) (response []byte, req.Header.Set("Content-Type", mimetype) req.Header.Set("Accept", mimetype) req.Header.Set("User-Agent", "Intra") - log.Debugf("%d Sending query", id) + logging.Debug.Printf("%d Sending query\n", id) httpResponse, err := t.client.Do(req) if err != nil { qerr = &queryError{SendFailed, err} return } - log.Debugf("%d Got response", id) - response, err = ioutil.ReadAll(httpResponse.Body) + logging.Debug.Printf("%d Got response\n", id) + response, err = io.ReadAll(httpResponse.Body) if err != nil { qerr = &queryError{BadResponse, err} return } httpResponse.Body.Close() - log.Debugf("%d Closed response", id) + logging.Debug.Printf("%d Closed response\n", id) // Update the hostname, which could have changed due to a redirect. hostname = httpResponse.Request.URL.Hostname() @@ -419,7 +423,7 @@ func (t *transport) sendRequest(id uint16, req *http.Request) (response []byte, req.Write(reqBuf) respBuf := new(bytes.Buffer) httpResponse.Write(respBuf) - log.Debugf("%d request: %s\nresponse: %s", id, reqBuf.String(), respBuf.String()) + logging.Debug.Printf("%d request: %s\nresponse: %s\n", id, reqBuf.String(), respBuf.String()) qerr = &queryError{HTTPError, &httpError{httpResponse.StatusCode}} return @@ -428,14 +432,14 @@ func (t *transport) sendRequest(id uint16, req *http.Request) (response []byte, return } -func (t *transport) Query(q []byte) ([]byte, error) { +func (t *transport) Query(ctx context.Context, q []byte) ([]byte, error) { var token Token if t.listener != nil { token = t.listener.OnQuery(t.url) } before := time.Now() - response, server, qerr := t.doQuery(q) + response, server, qerr := t.doQuery(ctx, q) after := time.Now() var err error @@ -477,7 +481,7 @@ func (t *transport) GetURL() string { // Perform a query using the transport, and send the response to the writer. func forwardQuery(t Transport, q []byte, c io.Writer) error { - resp, qerr := t.Query(q) + resp, qerr := t.Query(context.Background(), q) if resp == nil && qerr != nil { return qerr } @@ -504,7 +508,7 @@ func forwardQuery(t Transport, q []byte, c io.Writer) error { // and close the writer if there was an error. func forwardQueryAndCheck(t Transport, q []byte, c io.WriteCloser) { if err := forwardQuery(t, q, c); err != nil { - log.Warnf("Query forwarding failed: %v", err) + logging.Warn.Printf("Query forwarding failed: %v\n", err) c.Close() } } @@ -516,26 +520,26 @@ func Accept(t Transport, c io.ReadWriteCloser) { for { n, err := c.Read(qlbuf) if n == 0 { - log.Debugf("TCP query socket clean shutdown") + logging.Debug.Println("TCP query socket clean shutdown") break } if err != nil { - log.Warnf("Error reading from TCP query socket: %v", err) + logging.Warn.Printf("Error reading from TCP query socket: %v\n", err) break } if n < 2 { - log.Warnf("Incomplete query length") + logging.Warn.Println("Incomplete query length") break } qlen := binary.BigEndian.Uint16(qlbuf) q := make([]byte, qlen) n, err = c.Read(q) if err != nil { - log.Warnf("Error reading query: %v", err) + logging.Warn.Printf("Error reading query: %v\n", err) break } if n != int(qlen) { - log.Warnf("Incomplete query: %d < %d", n, qlen) + logging.Warn.Printf("Incomplete query: %d < %d\n", n, qlen) break } go forwardQueryAndCheck(t, q, c) @@ -560,7 +564,7 @@ func Servfail(q []byte) ([]byte, error) { func tryServfail(q []byte) []byte { response, err := Servfail(q) if err != nil { - log.Warnf("Error constructing servfail: %v", err) + logging.Warn.Printf("Error constructing servfail: %v\n", err) } return response } diff --git a/Android/app/src/go/intra/doh/doh_test.go b/Android/app/src/go/doh/doh_test.go similarity index 86% rename from Android/app/src/go/intra/doh/doh_test.go rename to Android/app/src/go/doh/doh_test.go index 05fc0aeb..c930be1c 100644 --- a/Android/app/src/go/intra/doh/doh_test.go +++ b/Android/app/src/go/doh/doh_test.go @@ -16,6 +16,7 @@ package doh import ( "bytes" + "context" "encoding/binary" "errors" "io" @@ -25,18 +26,12 @@ import ( "net/url" "reflect" "testing" + "time" "github.com/stretchr/testify/require" "golang.org/x/net/dns/dnsmessage" ) -var testURL = "https://dns.google/dns-query" -var ips = []string{ - "8.8.8.8", - "8.8.4.4", - "2001:4860:4860::8888", - "2001:4860:4860::8844", -} var parsedURL *url.URL var simpleQuery dnsmessage.Message = dnsmessage.Message{ @@ -123,15 +118,12 @@ var uncompressedQueryBytes []byte = []byte{ } func init() { - parsedURL, _ = url.Parse(testURL) + parsedURL, _ = url.Parse(googleDoH.url) } // Check that the constructor works. func TestNewTransport(t *testing.T) { - _, err := NewTransport(testURL, ips, nil, nil, nil) - if err != nil { - t.Fatal(err) - } + makeTestDoHTransport(t, googleDoH) } // Check that the constructor rejects unsupported URLs. @@ -149,8 +141,8 @@ func TestBadUrl(t *testing.T) { // Check for failure when the query is too short to be valid. func TestShortQuery(t *testing.T) { var qerr *queryError - doh, _ := NewTransport(testURL, ips, nil, nil, nil) - _, err := doh.Query([]byte{}) + doh := makeTestDoHTransport(t, googleDoH) + _, err := doh.Query(context.Background(), []byte{}) if err == nil { t.Error("Empty query should fail") } else if !errors.As(err, &qerr) { @@ -159,7 +151,7 @@ func TestShortQuery(t *testing.T) { t.Errorf("Wrong error status: %d", qerr.status) } - _, err = doh.Query([]byte{1}) + _, err = doh.Query(context.Background(), []byte{1}) if err == nil { t.Error("One byte query should fail") } else if !errors.As(err, &qerr) { @@ -187,12 +179,8 @@ func TestQueryIntegration(t *testing.T) { } testQuery := func(queryData []byte) { - - doh, err := NewTransport(testURL, ips, nil, nil, nil) - if err != nil { - t.Fatal(err) - } - resp, err2 := doh.Query(queryData) + doh := makeTestDoHTransport(t, googleDoH) + resp, err2 := doh.Query(context.Background(), queryData) if err2 != nil { t.Fatal(err2) } @@ -238,14 +226,14 @@ func (r *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) // Check that a DNS query is converted correctly into an HTTP query. func TestRequest(t *testing.T) { - doh, _ := NewTransport(testURL, ips, nil, nil, nil) + doh := makeTestDoHTransport(t, googleDoH) transport := doh.(*transport) rt := makeTestRoundTripper() transport.client.Transport = rt - go doh.Query(simpleQueryBytes) + go doh.Query(context.Background(), simpleQueryBytes) req := <-rt.req - if req.URL.String() != testURL { - t.Errorf("URL mismatch: %s != %s", req.URL.String(), testURL) + if req.URL.String() != googleDoH.url { + t.Errorf("URL mismatch: %s != %s", req.URL.String(), googleDoH.url) } reqBody, err := io.ReadAll(req.Body) if err != nil { @@ -287,7 +275,7 @@ func queriesMostlyEqual(m1 dnsmessage.Message, m2 dnsmessage.Message) bool { // Check that a DOH response is returned correctly. func TestResponse(t *testing.T) { - doh, _ := NewTransport(testURL, ips, nil, nil, nil) + doh := makeTestDoHTransport(t, googleDoH) transport := doh.(*transport) rt := makeTestRoundTripper() transport.client.Transport = rt @@ -308,7 +296,7 @@ func TestResponse(t *testing.T) { w.Close() }() - resp, err := doh.Query(simpleQueryBytes) + resp, err := doh.Query(context.Background(), simpleQueryBytes) if err != nil { t.Error(err) } @@ -326,7 +314,7 @@ func TestResponse(t *testing.T) { // Simulate an empty response. (This is not a compliant server // behavior.) func TestEmptyResponse(t *testing.T) { - doh, _ := NewTransport(testURL, ips, nil, nil, nil) + doh := makeTestDoHTransport(t, googleDoH) transport := doh.(*transport) rt := makeTestRoundTripper() transport.client.Transport = rt @@ -344,7 +332,7 @@ func TestEmptyResponse(t *testing.T) { } }() - _, err := doh.Query(simpleQueryBytes) + _, err := doh.Query(context.Background(), simpleQueryBytes) var qerr *queryError if err == nil { t.Error("Empty body should cause an error") @@ -357,7 +345,7 @@ func TestEmptyResponse(t *testing.T) { // Simulate a non-200 HTTP response code. func TestHTTPError(t *testing.T) { - doh, _ := NewTransport(testURL, ips, nil, nil, nil) + doh := makeTestDoHTransport(t, googleDoH) transport := doh.(*transport) rt := makeTestRoundTripper() transport.client.Transport = rt @@ -374,7 +362,7 @@ func TestHTTPError(t *testing.T) { w.Close() }() - _, err := doh.Query(simpleQueryBytes) + _, err := doh.Query(context.Background(), simpleQueryBytes) var qerr *queryError if err == nil { t.Error("Empty body should cause an error") @@ -387,13 +375,13 @@ func TestHTTPError(t *testing.T) { // Simulate an HTTP query error. func TestSendFailed(t *testing.T) { - doh, _ := NewTransport(testURL, ips, nil, nil, nil) + doh := makeTestDoHTransport(t, googleDoH) transport := doh.(*transport) rt := makeTestRoundTripper() transport.client.Transport = rt rt.err = errors.New("test") - _, err := doh.Query(simpleQueryBytes) + _, err := doh.Query(context.Background(), simpleQueryBytes) var qerr *queryError if err == nil { t.Error("Send failure should be reported") @@ -409,14 +397,14 @@ func TestSendFailed(t *testing.T) { // Test if DoH resolver IPs are confirmed and disconfirmed // when queries suceeded and fail, respectively. func TestDohIPConfirmDisconfirm(t *testing.T) { - u, _ := url.Parse(testURL) - doh, _ := NewTransport(testURL, ips, nil, nil, nil) + u, _ := url.Parse(googleDoH.url) + doh := makeTestDoHTransport(t, googleDoH) transport := doh.(*transport) hostname := u.Hostname() ipmap := transport.ips.Get(hostname) // send a valid request to first have confirmed-ip set - res, _ := doh.Query(simpleQueryBytes) + res, _ := doh.Query(context.Background(), simpleQueryBytes) mustUnpack(res) ip1 := ipmap.Confirmed() @@ -442,7 +430,7 @@ func TestDohIPConfirmDisconfirm(t *testing.T) { Request: &http.Request{URL: u}, } }() - doh.Query(simpleQueryBytes) + doh.Query(context.Background(), simpleQueryBytes) ip2 := ipmap.Confirmed() if ip2 != nil { @@ -450,19 +438,6 @@ func TestDohIPConfirmDisconfirm(t *testing.T) { } } -type fakeListener struct { - Listener - summary *Summary -} - -func (l *fakeListener) OnQuery(url string) Token { - return nil -} - -func (l *fakeListener) OnResponse(tok Token, summ *Summary) { - l.summary = summ -} - type fakeConn struct { net.TCPConn remoteAddr *net.TCPAddr @@ -474,8 +449,7 @@ func (c *fakeConn) RemoteAddr() net.Addr { // Check that the DNSListener is called with a correct summary. func TestListener(t *testing.T) { - listener := &fakeListener{} - doh, _ := NewTransport(testURL, ips, nil, nil, listener) + doh, listener := makeTestDoHTransportWithListener(t, googleDoH) transport := doh.(*transport) rt := makeTestRoundTripper() transport.client.Transport = rt @@ -500,7 +474,7 @@ func TestListener(t *testing.T) { w.Close() }() - doh.Query(simpleQueryBytes) + doh.Query(context.Background(), simpleQueryBytes) s := listener.summary if s.Latency < 0 { t.Errorf("Negative latency: %f", s.Latency) @@ -554,7 +528,7 @@ type fakeTransport struct { err error } -func (t *fakeTransport) Query(q []byte) ([]byte, error) { +func (t *fakeTransport) Query(ctx context.Context, q []byte) ([]byte, error) { t.query <- q if t.err != nil { return nil, t.err @@ -891,7 +865,75 @@ func TestServfail(t *testing.T) { } func TestQueryCanBeCancelled(t *testing.T) { - server, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)}) + expectDoHTimeout := func(server testingDoHServer, msg string) { + doh := makeTestDoHTransport(t, server) + st := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + doh.Query(ctx, simpleQueryBytes) + require.WithinRange(t, time.Now(), st.Add(1700*time.Millisecond), st.Add(2300*time.Millisecond), msg) + } + + expectDoHTimeout(unreachableDoH, "unreachable server should timeout within deadline of ctx") + + // Intentionally create a local DoH server that does not accept any requests + addr, err := url.Parse(localDoH.url) + require.NoError(t, err) + dohAddr, err := net.ResolveTCPAddr("tcp", addr.Host) + require.NoError(t, err) + svr, err := net.ListenTCP("tcp", dohAddr) + require.NoError(t, err) + defer svr.Close() + + expectDoHTimeout(localDoH, "unresponsive server should timeout within deadline of ctx") +} + +/******** Test DoH servers ********/ +type testingDoHServer struct { + url string + ips []string +} + +var unreachableDoH = testingDoHServer{ + url: "https://1.2.3.4:443", + ips: []string{"1.2.3.4"}, +} + +var googleDoH = testingDoHServer{ + url: "https://dns.google/dns-query", + ips: []string{ + "8.8.8.8", + "8.8.4.4", + "2001:4860:4860::8888", + "2001:4860:4860::8844", + }, +} + +var localDoH = testingDoHServer{ + url: "https://localhost:34443", + ips: []string{"127.0.0.1"}, +} + +/********** DoH Transport Test Helpers **********/ +type testingDoHListener struct { + Listener + summary *Summary +} + +func (l *testingDoHListener) OnQuery(url string) Token { return nil } +func (l *testingDoHListener) OnResponse(tok Token, s *Summary) { l.summary = s } + +func makeTestDoHTransport(t *testing.T, target testingDoHServer) Transport { + doh, err := NewTransport(target.url, target.ips, nil, nil, nil) + require.NoError(t, err) + require.NotNil(t, doh) + return doh +} + +func makeTestDoHTransportWithListener(t *testing.T, target testingDoHServer) (Transport, *testingDoHListener) { + listener := &testingDoHListener{} + doh, err := NewTransport(target.url, target.ips, nil, nil, listener) require.NoError(t, err) - defer server.Close() + require.NotNil(t, doh) + return doh, listener } diff --git a/Android/app/src/go/intra/doh/ipmap/ipmap.go b/Android/app/src/go/doh/ipmap/ipmap.go similarity index 97% rename from Android/app/src/go/intra/doh/ipmap/ipmap.go rename to Android/app/src/go/doh/ipmap/ipmap.go index 9c94a5db..479ba72c 100644 --- a/Android/app/src/go/intra/doh/ipmap/ipmap.go +++ b/Android/app/src/go/doh/ipmap/ipmap.go @@ -20,7 +20,7 @@ import ( "net" "sync" - "github.com/eycorsican/go-tun2socks/common/log" + "localhost/Intra/Android/app/src/go/logging" ) // IPMap maps hostnames to IPSets. @@ -104,7 +104,7 @@ func (s *IPSet) Add(hostname string) { // Don't hold the ipMap lock during blocking I/O. resolved, err := s.r.LookupIPAddr(context.TODO(), hostname) if err != nil { - log.Warnf("Failed to resolve %s: %v", hostname, err) + logging.Warn.Printf("Failed to resolve %s: %v\n", hostname, err) } s.Lock() for _, addr := range resolved { diff --git a/Android/app/src/go/intra/doh/ipmap/ipmap_test.go b/Android/app/src/go/doh/ipmap/ipmap_test.go similarity index 100% rename from Android/app/src/go/intra/doh/ipmap/ipmap_test.go rename to Android/app/src/go/doh/ipmap/ipmap_test.go diff --git a/Android/app/src/go/intra/doh/padding.go b/Android/app/src/go/doh/padding.go similarity index 100% rename from Android/app/src/go/intra/doh/padding.go rename to Android/app/src/go/doh/padding.go diff --git a/Android/app/src/go/intra/android/tun2socks.go b/Android/app/src/go/intra/android/tun2socks.go deleted file mode 100644 index df483d47..00000000 --- a/Android/app/src/go/intra/android/tun2socks.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2023 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tun2socks - -import ( - "errors" - "io" - "io/fs" - "log" - "os" - "strings" - - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/doh" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/protect" - "github.com/Jigsaw-Code/outline-sdk/network" -) - -// ConnectIntraTunnel reads packets from a TUN device and applies the Intra routing -// rules. Currently, this only consists of redirecting DNS packets to a specified -// server; all other data flows directly to its destination. -// -// `fd` is the TUN device. The IntraTunnel acquires an additional reference to it, which -// -// is released by IntraTunnel.Disconnect(), so the caller must close `fd` _and_ call -// Disconnect() in order to close the TUN device. -// -// `fakedns` is the DNS server that the system believes it is using, in "host:port" style. -// -// The port is normally 53. -// -// `udpdns` and `tcpdns` are the location of the actual DNS server being used. For DNS -// -// tunneling in Intra, these are typically high-numbered ports on localhost. -// -// `dohdns` is the initial DoH transport. It must not be `nil`. -// `protector` is a wrapper for Android's VpnService.protect() method. -// `eventListener` will be provided with a summary of each TCP and UDP socket when it is closed. -// -// Throws an exception if the TUN file descriptor cannot be opened, or if the tunnel fails to -// connect. -func ConnectIntraTunnel( - fd int, fakedns string, dohdns doh.Transport, protector protect.Protector, eventListener intra.Listener, -) (*intra.Tunnel, error) { - tun, err := makeTunFile(fd) - if err != nil { - return nil, err - } - t, err := intra.NewTunnel(fakedns, dohdns, tun, protector, eventListener) - if err != nil { - return nil, err - } - go copyUntilEOF(t, tun) - go copyUntilEOF(tun, t) - return t, nil -} - -// NewDoHTransport returns a DNSTransport that connects to the specified DoH server. -// `url` is the URL of a DoH server (no template, POST-only). If it is nonempty, it -// -// overrides `udpdns` and `tcpdns`. -// -// `ips` is an optional comma-separated list of IP addresses for the server. (This -// -// wrapper is required because gomobile can't make bindings for []string.) -// -// `protector` is the socket protector to use for all external network activity. -// `auth` will provide a client certificate if required by the TLS server. -// `eventListener` will be notified after each DNS query succeeds or fails. -func NewDoHTransport( - url string, ips string, protector protect.Protector, auth doh.ClientAuth, eventListener intra.Listener, -) (doh.Transport, error) { - split := []string{} - if len(ips) > 0 { - split = strings.Split(ips, ",") - } - dialer := protect.MakeDialer(protector) - return doh.NewTransport(url, split, dialer, auth, eventListener) -} - -func copyUntilEOF(dst, src io.ReadWriteCloser) { - log.Printf("[debug] start relaying traffic [%s] -> [%s]", src, dst) - defer log.Printf("[debug] stop relaying traffic [%s] -> [%s]", src, dst) - - const commonMTU = 1500 - buf := make([]byte, commonMTU) - defer dst.Close() - for { - _, err := io.CopyBuffer(dst, src, buf) - if err == nil || isErrClosed(err) { - return - } - } -} - -func isErrClosed(err error) bool { - return errors.Is(err, os.ErrClosed) || errors.Is(err, fs.ErrClosed) || errors.Is(err, network.ErrClosed) -} diff --git a/Android/app/src/go/intra/doh/atomic.go b/Android/app/src/go/intra/doh/atomic.go deleted file mode 100644 index c0a179c9..00000000 --- a/Android/app/src/go/intra/doh/atomic.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2023 Jigsaw Operations LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package doh - -import ( - "sync/atomic" -) - -// Atomic is atomic.Value, specialized for doh.Transport. -type Atomic struct { - v atomic.Value -} - -// Store a DNSTransport. d must not be nil. -func (a *Atomic) Store(t Transport) { - a.v.Store(t) -} - -// Load the DNSTransport, or nil if it has not been stored. -func (a *Atomic) Load() Transport { - v := a.v.Load() - if v == nil { - return nil - } - return v.(Transport) -} diff --git a/Android/app/src/go/intra/split/example/main.go b/Android/app/src/go/intra/split/example/main.go index 9644e5f1..445cfc9d 100644 --- a/Android/app/src/go/intra/split/example/main.go +++ b/Android/app/src/go/intra/split/example/main.go @@ -22,7 +22,7 @@ import ( "net" "os" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/split" + "localhost/Intra/Android/app/src/go/intra/split" ) func main() { diff --git a/Android/app/src/go/internal/logging/logging.go b/Android/app/src/go/logging/logging.go similarity index 93% rename from Android/app/src/go/internal/logging/logging.go rename to Android/app/src/go/logging/logging.go index 0a6cd806..062dd3a9 100644 --- a/Android/app/src/go/internal/logging/logging.go +++ b/Android/app/src/go/logging/logging.go @@ -21,6 +21,6 @@ import ( ) var Debug = log.New(io.Discard, "[DEBUG] ", log.LstdFlags) -var Info = log.New(os.Stdout, "[INFO] ", log.LstdFlags) +var Info = log.New(io.Discard, "[INFO] ", log.LstdFlags) var Warn = log.New(os.Stderr, "[WARN] ", log.LstdFlags) var Err = log.New(os.Stderr, "[ERROR] ", log.LstdFlags) diff --git a/Android/app/src/go/intra/android/tun.go b/Android/app/src/go/tuntap/tun.go similarity index 90% rename from Android/app/src/go/intra/android/tun.go rename to Android/app/src/go/tuntap/tun.go index e3639dc9..c47b5aac 100644 --- a/Android/app/src/go/intra/android/tun.go +++ b/Android/app/src/go/tuntap/tun.go @@ -1,4 +1,4 @@ -// Copyright 2023 Jigsaw Operations LLC +// Copyright 2024 Jigsaw Operations LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package tun2socks +package tuntap import ( "errors" @@ -21,7 +21,7 @@ import ( "golang.org/x/sys/unix" ) -func makeTunFile(fd int) (*os.File, error) { +func MakeTunDeviceFromFD(fd int) (*os.File, error) { if fd < 0 { return nil, errors.New("must provide a valid TUN file descriptor") } diff --git a/go.mod b/go.mod index 6fb7b038..9f6368f2 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/Jigsaw-Code/Intra +module localhost/Intra go 1.21.1 @@ -6,7 +6,6 @@ require ( github.com/Jigsaw-Code/choir v1.0.1 github.com/Jigsaw-Code/getsni v1.0.0 github.com/Jigsaw-Code/outline-sdk v0.0.7 - github.com/eycorsican/go-tun2socks v1.16.11 github.com/stretchr/testify v1.8.2 golang.org/x/mobile v0.0.0-20231006135142-2b44d11868fe golang.org/x/net v0.16.0 @@ -15,6 +14,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/eycorsican/go-tun2socks v1.16.11 // indirect github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/crypto v0.14.0 // indirect From 0abb4ad79c79c3b185a1bf315a0650d4a40b0064 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 20 Feb 2024 19:03:28 -0500 Subject: [PATCH 05/17] using the new logging package throughout the project --- Android/app/src/go/intra/protect/protect.go | 5 ++--- Android/app/src/go/intra/sni_reporter.go | 18 ++++++++++-------- Android/app/src/go/intra/sni_reporter_test.go | 7 ++++--- Android/app/src/go/intra/split/retrier.go | 13 +++++++++++-- Android/app/src/go/intra/split/retrier_test.go | 3 ++- Android/app/src/go/intra/stream_dialer.go | 9 +++++---- Android/app/src/go/intra/tcp.go | 3 ++- 7 files changed, 36 insertions(+), 22 deletions(-) diff --git a/Android/app/src/go/intra/protect/protect.go b/Android/app/src/go/intra/protect/protect.go index f3d29acc..ee669d8c 100644 --- a/Android/app/src/go/intra/protect/protect.go +++ b/Android/app/src/go/intra/protect/protect.go @@ -18,11 +18,10 @@ import ( "context" "errors" "fmt" + "localhost/Intra/Android/app/src/go/logging" "net" "strings" "syscall" - - "github.com/eycorsican/go-tun2socks/common/log" ) // Protector provides the ability to bypass a VPN on Android, pre-Lollipop. @@ -45,7 +44,7 @@ func makeControl(p Protector) func(string, string, syscall.RawConn) error { return c.Control(func(fd uintptr) { if !p.Protect(int32(fd)) { // TODO: Record and report these errors. - log.Errorf("Failed to protect a %s socket", network) + logging.Err.Printf("Failed to protect a %s socket\n", network) } }) } diff --git a/Android/app/src/go/intra/sni_reporter.go b/Android/app/src/go/intra/sni_reporter.go index 4346721a..9472fc34 100644 --- a/Android/app/src/go/intra/sni_reporter.go +++ b/Android/app/src/go/intra/sni_reporter.go @@ -15,13 +15,15 @@ package intra import ( + "context" "io" "sync" "time" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/doh" + "localhost/Intra/Android/app/src/go/doh" + "localhost/Intra/Android/app/src/go/logging" + "github.com/Jigsaw-Code/choir" - "github.com/eycorsican/go-tun2socks/common/log" ) // Number of bins to assign reports to. Should be large enough for @@ -60,11 +62,11 @@ func (r *tcpSNIReporter) Send(report choir.Report) error { r.mu.RUnlock() q, err := choir.FormatQuery(report, suffix) if err != nil { - log.Warnf("Failed to construct query for Choir: %v", err) + logging.Warn.Printf("Failed to construct query for Choir: %v\n", err) return nil } - if _, err = dns.Query(q); err != nil { - log.Infof("Failed to deliver query for Choir: %v", err) + if _, err = dns.Query(context.Background(), q); err != nil { + logging.Info.Printf("Failed to deliver query for Choir: %v\n", err) } return nil } @@ -104,13 +106,13 @@ func (r *tcpSNIReporter) Report(summary TCPSocketSummary) { } resultValue, err := choir.NewValue(result) if err != nil { - log.Fatalf("Bad result %s: %v", result, err) + logging.Err.Printf("Bad result %s: %v\n", result, err) } responseValue, err := choir.NewValue(response) if err != nil { - log.Fatalf("Bad response %s: %v", response, err) + logging.Err.Printf("Bad response %s: %v\n", response, err) } if err := reporter.Report(summary.Retry.SNI, resultValue, responseValue); err != nil { - log.Warnf("Choir report failed: %v", err) + logging.Warn.Printf("Choir report failed: %v\n", err) } } diff --git a/Android/app/src/go/intra/sni_reporter_test.go b/Android/app/src/go/intra/sni_reporter_test.go index 9826dd1c..0e878a7a 100644 --- a/Android/app/src/go/intra/sni_reporter_test.go +++ b/Android/app/src/go/intra/sni_reporter_test.go @@ -16,14 +16,15 @@ package intra import ( "bytes" + "context" "errors" "strings" "testing" "golang.org/x/net/dns/dnsmessage" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/doh" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/split" + "localhost/Intra/Android/app/src/go/doh" + "localhost/Intra/Android/app/src/go/intra/split" ) type qfunc func(q []byte) ([]byte, error) @@ -33,7 +34,7 @@ type fakeTransport struct { query qfunc } -func (t *fakeTransport) Query(q []byte) ([]byte, error) { +func (t *fakeTransport) Query(ctx context.Context, q []byte) ([]byte, error) { return t.query(q) } diff --git a/Android/app/src/go/intra/split/retrier.go b/Android/app/src/go/intra/split/retrier.go index da24065a..7334e7ae 100644 --- a/Android/app/src/go/intra/split/retrier.go +++ b/Android/app/src/go/intra/split/retrier.go @@ -15,6 +15,7 @@ package split import ( + "context" "errors" "io" "math/rand" @@ -22,6 +23,8 @@ import ( "sync" "time" + "localhost/Intra/Android/app/src/go/logging" + "github.com/Jigsaw-Code/getsni" ) @@ -117,9 +120,11 @@ const DefaultTimeout time.Duration = 0 // `dialer` will be used to establish the connection. // `addr` is the destination. // If `stats` is non-nil, it will be populated with retry-related information. -func DialWithSplitRetry(dialer *net.Dialer, addr *net.TCPAddr, stats *RetryStats) (DuplexConn, error) { +func DialWithSplitRetry(ctx context.Context, dialer *net.Dialer, addr *net.TCPAddr, stats *RetryStats) (DuplexConn, error) { + logging.Debug.Printf("Split-Retry: dialing to %v...\n", addr) before := time.Now() - conn, err := dialer.Dial(addr.Network(), addr.String()) + conn, err := dialer.DialContext(ctx, addr.Network(), addr.String()) + logging.Debug.Printf("Split-Retry: conn dialed, err = %v\n", err) if err != nil { return nil, err } @@ -162,6 +167,7 @@ func (r *retrier) Read(buf []byte) (n int, err error) { // Read failed. Retry. n, err = r.retry(buf) } + logging.Debug.Println("Split-Retry: direct conn succeeded, no need to split") close(r.retryCompleteFlag) // Unset read deadline. r.conn.SetReadDeadline(time.Time{}) @@ -172,6 +178,9 @@ func (r *retrier) Read(buf []byte) (n int, err error) { } func (r *retrier) retry(buf []byte) (n int, err error) { + logging.Debug.Println("Split-Retry: retrying...") + defer func() { logging.Debug.Printf("Split-Retry: retried n = %v, err = %v\n", n, err) }() + r.conn.Close() var newConn net.Conn if newConn, err = r.dialer.Dial(r.addr.Network(), r.addr.String()); err != nil { diff --git a/Android/app/src/go/intra/split/retrier_test.go b/Android/app/src/go/intra/split/retrier_test.go index d539c2e9..c68c542a 100644 --- a/Android/app/src/go/intra/split/retrier_test.go +++ b/Android/app/src/go/intra/split/retrier_test.go @@ -16,6 +16,7 @@ package split import ( "bytes" + "context" "io" "net" "testing" @@ -46,7 +47,7 @@ func makeSetup(t *testing.T) *setup { t.Error("Server isn't TCP?") } var stats RetryStats - clientSide, err := DialWithSplitRetry(&net.Dialer{}, serverAddr, &stats) + clientSide, err := DialWithSplitRetry(context.Background(), &net.Dialer{}, serverAddr, &stats) if err != nil { t.Error(err) } diff --git a/Android/app/src/go/intra/stream_dialer.go b/Android/app/src/go/intra/stream_dialer.go index 2d27466d..68d8c7a3 100644 --- a/Android/app/src/go/intra/stream_dialer.go +++ b/Android/app/src/go/intra/stream_dialer.go @@ -23,9 +23,10 @@ import ( "sync/atomic" "time" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/doh" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/protect" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/split" + "localhost/Intra/Android/app/src/go/doh" + "localhost/Intra/Android/app/src/go/intra/protect" + "localhost/Intra/Android/app/src/go/intra/split" + "github.com/Jigsaw-Code/outline-sdk/transport" ) @@ -99,7 +100,7 @@ func (sd *intraStreamDialer) dial(ctx context.Context, dest netip.AddrPort, stat return split.DialWithSplit(sd.dialer, net.TCPAddrFromAddrPort(dest)) } else { stats.Retry = &split.RetryStats{} - return split.DialWithSplitRetry(sd.dialer, net.TCPAddrFromAddrPort(dest), stats.Retry) + return split.DialWithSplitRetry(ctx, sd.dialer, net.TCPAddrFromAddrPort(dest), stats.Retry) } } else { tcpsd := &transport.TCPStreamDialer{ diff --git a/Android/app/src/go/intra/tcp.go b/Android/app/src/go/intra/tcp.go index f61631d2..f50e3a4d 100644 --- a/Android/app/src/go/intra/tcp.go +++ b/Android/app/src/go/intra/tcp.go @@ -23,7 +23,8 @@ import ( "sync/atomic" "time" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/split" + "localhost/Intra/Android/app/src/go/intra/split" + "github.com/Jigsaw-Code/outline-sdk/transport" ) From 4ee08690ee7451f4b72d1df902a45b4c2dcef7e7 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 20 Feb 2024 19:03:51 -0500 Subject: [PATCH 06/17] update Java code to use the new backend package --- .../main/java/app/intra/net/doh/Prober.java | 16 ------ .../src/main/java/app/intra/net/doh/Race.java | 5 ++ .../app/intra/net/go/GoIntraListener.java | 56 ++++++++++--------- .../main/java/app/intra/net/go/GoProber.java | 16 +----- .../java/app/intra/net/go/GoVpnAdapter.java | 33 +++++------ 5 files changed, 54 insertions(+), 72 deletions(-) diff --git a/Android/app/src/main/java/app/intra/net/doh/Prober.java b/Android/app/src/main/java/app/intra/net/doh/Prober.java index 411c7d80..5114dff5 100644 --- a/Android/app/src/main/java/app/intra/net/doh/Prober.java +++ b/Android/app/src/main/java/app/intra/net/doh/Prober.java @@ -19,22 +19,6 @@ * A prober can perform asynchronous checks to determine whether a DOH server is working. */ public abstract class Prober { - - protected static final byte[] QUERY_DATA = { - 0, 0, // [0-1] query ID - 1, 0, // [2-3] flags, RD=1 - 0, 1, // [4-5] QDCOUNT (number of queries) = 1 - 0, 0, // [6-7] ANCOUNT (number of answers) = 0 - 0, 0, // [8-9] NSCOUNT (number of authoritative answers) = 0 - 0, 0, // [10-11] ARCOUNT (number of additional records) = 0 - // Start of first query - 7, 'y', 'o', 'u', 't', 'u', 'b', 'e', - 3, 'c', 'o', 'm', - 0, // null terminator of FQDN (DNS root) - 0, 1, // QTYPE = A - 0, 1 // QCLASS = IN (Internet) - }; - public interface Callback { void onCompleted(boolean succeeded); } diff --git a/Android/app/src/main/java/app/intra/net/doh/Race.java b/Android/app/src/main/java/app/intra/net/doh/Race.java index 987da428..fd13bf39 100644 --- a/Android/app/src/main/java/app/intra/net/doh/Race.java +++ b/Android/app/src/main/java/app/intra/net/doh/Race.java @@ -16,6 +16,8 @@ package app.intra.net.doh; import android.content.Context; +import android.util.Log; + import app.intra.net.go.GoProber; /** @@ -23,6 +25,7 @@ * the fastest probe succeeds or all probes have failed. Each instance can only be used once. */ public class Race { + private static final String TAG = "DoHProbe"; // tag for logging public interface Listener { /** @@ -64,11 +67,13 @@ private static class Collector { synchronized void onCompleted(int index, boolean succeeded) { if (succeeded) { + Log.i(TAG, "DoH Server No. " + index + ": succeeded"); if (!reportedSuccess) { listener.onResult(index); reportedSuccess = true; } } else { + Log.w(TAG, "DoH Server No. " + index + ": failed"); ++numFailed; if (numFailed == numCallbacks) { // All probes failed diff --git a/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java b/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java index a84bbae8..ab756235 100644 --- a/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java +++ b/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java @@ -17,19 +17,21 @@ import android.os.SystemClock; import androidx.collection.LongSparseArray; +import com.google.firebase.perf.FirebasePerformance; +import com.google.firebase.perf.metrics.HttpMetric; +import java.net.ProtocolException; +import java.util.Calendar; import app.intra.net.dns.DnsPacket; import app.intra.net.doh.Transaction; import app.intra.net.doh.Transaction.Status; -import app.intra.sys.firebase.AnalyticsWrapper; import app.intra.sys.IntraVpnService; -import com.google.firebase.perf.FirebasePerformance; -import com.google.firebase.perf.metrics.HttpMetric; -import doh.Doh; -import doh.Token; +import app.intra.sys.firebase.AnalyticsWrapper; +import backend.Backend; +import backend.DoHListener; +import backend.DoHQueryStats; +import backend.DoHQueryToken; import intra.TCPSocketSummary; import intra.UDPSocketSummary; -import java.net.ProtocolException; -import java.util.Calendar; import split.RetryStats; /** @@ -37,7 +39,7 @@ * when a socket has concluded, with performance metrics for that socket, and this class forwards * those metrics to Firebase. */ -public class GoIntraListener implements intra.Listener { +public class GoIntraListener implements intra.Listener, DoHListener { // UDP is often used for one-off messages and pings. The relative overhead of reporting metrics // on these short messages would be large, so we only report metrics on sockets that transfer at @@ -91,12 +93,12 @@ public void onUDPSocketClosed(UDPSocketSummary summary) { private static final LongSparseArray goStatusMap = new LongSparseArray<>(); static { - goStatusMap.put(Doh.Complete, Status.COMPLETE); - goStatusMap.put(Doh.SendFailed, Status.SEND_FAIL); - goStatusMap.put(Doh.HTTPError, Status.HTTP_ERROR); - goStatusMap.put(Doh.BadQuery, Status.INTERNAL_ERROR); // TODO: Add a BAD_QUERY Status - goStatusMap.put(Doh.BadResponse, Status.BAD_RESPONSE); - goStatusMap.put(Doh.InternalError, Status.INTERNAL_ERROR); + goStatusMap.put(Backend.DoHStatusComplete, Status.COMPLETE); + goStatusMap.put(Backend.DoHStatusSendFailed, Status.SEND_FAIL); + goStatusMap.put(Backend.DoHStatusHTTPError, Status.HTTP_ERROR); + goStatusMap.put(Backend.DoHStatusBadQuery, Status.INTERNAL_ERROR); // TODO: Add a BAD_QUERY Status + goStatusMap.put(Backend.DoHStatusBadResponse, Status.BAD_RESPONSE); + goStatusMap.put(Backend.DoHStatusInternalError, Status.INTERNAL_ERROR); } // Wrapping HttpMetric into a doh.Token allows us to get paired query and response notifications @@ -104,7 +106,7 @@ public void onUDPSocketClosed(UDPSocketSummary summary) { // required by the structure of the HttpMetric API (which does not have any other way to record // latency), and reverse binding is worth avoiding, especially because it's not compatible with // the Go module system (https://github.com/golang/go/issues/27234). - private class Metric implements doh.Token { + private static class Metric implements DoHQueryToken { final HttpMetric metric; Metric(String url) { metric = FirebasePerformance.getInstance().newHttpMetric(url, "POST"); @@ -112,7 +114,7 @@ private class Metric implements doh.Token { } @Override - public Token onQuery(String url) { + public DoHQueryToken onQuery(String url) { Metric m = new Metric(url); m.metric.start(); return m; @@ -123,30 +125,30 @@ private static int len(byte[] a) { } @Override - public void onResponse(Token token, doh.Summary summary) { - if (summary.getHTTPStatus() != 0 && token != null) { + public void onResponse(DoHQueryToken token, DoHQueryStats stats) { + if (stats.getHTTPStatus() != 0 && token != null) { // HTTP transaction completed. Report performance metrics. Metric m = (Metric)token; - m.metric.setRequestPayloadSize(len(summary.getQuery())); - m.metric.setHttpResponseCode((int)summary.getHTTPStatus()); - m.metric.setResponsePayloadSize(len(summary.getResponse())); + m.metric.setRequestPayloadSize(len(stats.getQuery())); + m.metric.setHttpResponseCode((int)stats.getHTTPStatus()); + m.metric.setResponsePayloadSize(len(stats.getResponse())); m.metric.stop(); // Finalizes the metric and queues it for upload. } final DnsPacket query; try { - query = new DnsPacket(summary.getQuery()); + query = new DnsPacket(stats.getQuery()); } catch (ProtocolException e) { return; } - long latencyMs = (long)(1000 * summary.getLatency()); + long latencyMs = (long)(1000 * stats.getLatency()); long nowMs = SystemClock.elapsedRealtime(); long queryTimeMs = nowMs - latencyMs; Transaction transaction = new Transaction(query, queryTimeMs); - transaction.response = summary.getResponse(); - transaction.responseTime = (long)(1000 * summary.getLatency()); - transaction.serverIp = summary.getServer(); - transaction.status = goStatusMap.get(summary.getStatus()); + transaction.response = stats.getResponse(); + transaction.responseTime = (long)(1000 * stats.getLatency()); + transaction.serverIp = stats.getServer(); + transaction.status = goStatusMap.get(stats.getStatus()); transaction.responseCalendar = Calendar.getInstance(); vpnService.recordTransaction(transaction); diff --git a/Android/app/src/main/java/app/intra/net/go/GoProber.java b/Android/app/src/main/java/app/intra/net/go/GoProber.java index 4474f13a..033e9888 100644 --- a/Android/app/src/main/java/app/intra/net/go/GoProber.java +++ b/Android/app/src/main/java/app/intra/net/go/GoProber.java @@ -20,9 +20,8 @@ import android.os.Build.VERSION_CODES; import app.intra.net.doh.Prober; import app.intra.sys.VpnController; -import doh.Transport; +import backend.DoHServer; import protect.Protector; -import tun2socks.Tun2socks; /** * Implements a Probe using the Go-based DoH client. @@ -43,17 +42,8 @@ public void probe(String url, Callback callback) { // Protection isn't needed for Lollipop+, or if the VPN is not active. Protector protector = VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP ? null : VpnController.getInstance().getIntraVpnService(); - Transport transport = Tun2socks.newDoHTransport(url, dohIPs, protector, null, null); - if (transport == null) { - callback.onCompleted(false); - return; - } - byte[] response = transport.query(QUERY_DATA); - if (response != null && response.length > 0) { - callback.onCompleted(true); - return; - } - callback.onCompleted(false); + new DoHServer(url, dohIPs, protector, null).probe(); + callback.onCompleted(true); } catch (Exception e) { callback.onCompleted(false); } diff --git a/Android/app/src/main/java/app/intra/net/go/GoVpnAdapter.java b/Android/app/src/main/java/app/intra/net/go/GoVpnAdapter.java index 9abc7f46..29c0220a 100644 --- a/Android/app/src/main/java/app/intra/net/go/GoVpnAdapter.java +++ b/Android/app/src/main/java/app/intra/net/go/GoVpnAdapter.java @@ -25,6 +25,11 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Locale; import app.intra.R; import app.intra.sys.CountryCode; import app.intra.sys.IntraVpnService; @@ -33,14 +38,10 @@ import app.intra.sys.firebase.AnalyticsWrapper; import app.intra.sys.firebase.LogWrapper; import app.intra.sys.firebase.RemoteConfig; -import doh.Transport; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Locale; +import backend.Backend; +import backend.DoHServer; +import backend.Session; import protect.Protector; -import tun2socks.Tun2socks; /** * This is a VpnAdapter that captures all traffic and routes it through a go-tun2socks instance with @@ -87,7 +88,7 @@ String make(String template) { private ParcelFileDescriptor tunFd; // The Intra session object from go-tun2socks. Initially null. - private intra.Tunnel tunnel; + private Session tunnel; private GoIntraListener listener; public static GoVpnAdapter establish(@NonNull IntraVpnService vpnService) { @@ -120,10 +121,10 @@ private void connectTunnel() { try { LogWrapper.log(Log.INFO, LOG_TAG, "Starting go-tun2socks"); - Transport transport = makeDohTransport(dohURL); + DoHServer server = makeDoHServer(dohURL); // connectIntraTunnel makes a copy of the file descriptor. - tunnel = Tun2socks.connectIntraTunnel(tunFd.getFd(), fakeDns, - transport, getProtector(), listener); + tunnel = Backend.connectSession(tunFd.getFd(), fakeDns, + server, getProtector(), listener); } catch (Exception e) { LogWrapper.logException(e); VpnController.getInstance().onConnectionStateChanged(vpnService, IntraVpnService.State.FAILING); @@ -201,21 +202,21 @@ public synchronized void close() { tunFd = null; } - private doh.Transport makeDohTransport(@Nullable String url) throws Exception { + private DoHServer makeDoHServer(@Nullable String url) throws Exception { @NonNull String realUrl = PersistentState.expandUrl(vpnService, url); String dohIPs = getIpString(vpnService, realUrl); String host = new URL(realUrl).getHost(); long startTime = SystemClock.elapsedRealtime(); - final doh.Transport transport; + final DoHServer server; try { - transport = Tun2socks.newDoHTransport(realUrl, dohIPs, getProtector(), null, listener); + server = new DoHServer(realUrl, dohIPs, getProtector(), listener); } catch (Exception e) { AnalyticsWrapper.get(vpnService).logBootstrapFailed(host); throw e; } int delta = (int) (SystemClock.elapsedRealtime() - startTime); AnalyticsWrapper.get(vpnService).logBootstrap(host, delta); - return transport; + return server; } /** @@ -240,7 +241,7 @@ public synchronized void updateDohUrl() { // out. String url = PersistentState.getServerUrl(vpnService); try { - tunnel.setDNS(makeDohTransport(url)); + tunnel.setDoHServer(makeDoHServer(url)); } catch (Exception e) { LogWrapper.logException(e); tunnel.disconnect(); From 43de89898708a070074e8b968827f8d400cd1a20 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 20 Feb 2024 21:18:19 -0500 Subject: [PATCH 07/17] fix another deadlock in OnResponse --- Android/app/src/go/doh/doh.go | 15 ++++++++++++++- Android/app/src/go/intra/packet_proxy.go | 12 ++++++++---- Android/app/src/go/intra/tunnel.go | 21 +++++++++++++-------- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Android/app/src/go/doh/doh.go b/Android/app/src/go/doh/doh.go index 6f22eaf2..dac36b97 100644 --- a/Android/app/src/go/doh/doh.go +++ b/Android/app/src/go/doh/doh.go @@ -456,7 +456,19 @@ func (t *transport) Query(ctx context.Context, q []byte) ([]byte, error) { } } - if t.listener != nil { + // Stop sending OnResponse when the error is cancelled because the cancelled + // error is typically triggered by a Disconnect() operation, which will cause + // the following deadlock: + // 1. Java - synchronized VpnController.stop() + // 2. Go - context.Cancel() + // 3. Go - errors.Is(qerr, context.Cancelled) + // 4. Go - (if we don't stop sending OnResponse) + // 5. Java - GoIntraListener.onResponse + // 6. Java - synchronized VpnController.onConnectionStateChanged() + // Deadlock happens (both Step 1 and Step 6 are marked as synchronized)! + // + // TODO: make stop() an asynchronized function + if t.listener != nil && !errors.Is(qerr, context.Canceled) { latency := after.Sub(before) var ip string if server != nil { @@ -550,6 +562,7 @@ func Accept(t Transport, c io.ReadWriteCloser) { // Servfail returns a SERVFAIL response to the query q. func Servfail(q []byte) ([]byte, error) { + defer logging.Debug.Println("SERVFAIL response generated") var msg dnsmessage.Message if err := msg.Unpack(q); err != nil { return nil, err diff --git a/Android/app/src/go/intra/packet_proxy.go b/Android/app/src/go/intra/packet_proxy.go index 5232857a..1bd28481 100644 --- a/Android/app/src/go/intra/packet_proxy.go +++ b/Android/app/src/go/intra/packet_proxy.go @@ -15,6 +15,7 @@ package intra import ( + "context" "errors" "fmt" "net" @@ -22,8 +23,9 @@ import ( "sync/atomic" "time" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/doh" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/protect" + "localhost/Intra/Android/app/src/go/doh" + "localhost/Intra/Android/app/src/go/intra/protect" + "github.com/Jigsaw-Code/outline-sdk/network" "github.com/Jigsaw-Code/outline-sdk/transport" ) @@ -33,12 +35,13 @@ type intraPacketProxy struct { dns atomic.Pointer[doh.Transport] proxy network.PacketProxy listener UDPListener + ctx context.Context } var _ network.PacketProxy = (*intraPacketProxy)(nil) func newIntraPacketProxy( - fakeDNS netip.AddrPort, dns doh.Transport, protector protect.Protector, listener UDPListener, + ctx context.Context, fakeDNS netip.AddrPort, dns doh.Transport, protector protect.Protector, listener UDPListener, ) (*intraPacketProxy, error) { if dns == nil { return nil, errors.New("dns is required") @@ -58,6 +61,7 @@ func newIntraPacketProxy( fakeDNSAddr: fakeDNS, proxy: pp, listener: listener, + ctx: ctx, } dohpp.dns.Store(&dns) @@ -122,7 +126,7 @@ func (req *dohPacketReqSender) WriteTo(p []byte, destination netip.AddrPort) (in } }() - resp, err := (*req.proxy.dns.Load()).Query(p) + resp, err := (*req.proxy.dns.Load()).Query(req.proxy.ctx, p) if err != nil { return 0, fmt.Errorf("DoH request error: %w", err) } diff --git a/Android/app/src/go/intra/tunnel.go b/Android/app/src/go/intra/tunnel.go index 0d79e022..eccb279e 100644 --- a/Android/app/src/go/intra/tunnel.go +++ b/Android/app/src/go/intra/tunnel.go @@ -15,6 +15,7 @@ package intra import ( + "context" "errors" "fmt" "io" @@ -22,8 +23,9 @@ import ( "os" "strings" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/doh" - "github.com/Jigsaw-Code/Intra/Android/app/src/go/intra/protect" + "localhost/Intra/Android/app/src/go/doh" + "localhost/Intra/Android/app/src/go/intra/protect" + "github.com/Jigsaw-Code/outline-sdk/network" "github.com/Jigsaw-Code/outline-sdk/network/lwip2transport" ) @@ -33,17 +35,18 @@ import ( type Listener interface { UDPListener TCPListener - doh.Listener } // Tunnel represents an Intra session. type Tunnel struct { network.IPDevice - sd *intraStreamDialer - pp *intraPacketProxy - sni *tcpSNIReporter - tun io.Closer + ctx context.Context + cancel context.CancelFunc + sd *intraStreamDialer + pp *intraPacketProxy + sni *tcpSNIReporter + tun io.Closer } // NewTunnel creates a connected Intra session. @@ -76,13 +79,14 @@ func NewTunnel( }, tun: tun, } + t.ctx, t.cancel = context.WithCancel(context.Background()) t.sd, err = newIntraStreamDialer(fakeDNSAddr.AddrPort(), dohdns, protector, eventListener, t.sni) if err != nil { return nil, fmt.Errorf("failed to create stream dialer: %w", err) } - t.pp, err = newIntraPacketProxy(fakeDNSAddr.AddrPort(), dohdns, protector, eventListener) + t.pp, err = newIntraPacketProxy(t.ctx, fakeDNSAddr.AddrPort(), dohdns, protector, eventListener) if err != nil { return nil, fmt.Errorf("failed to create packet proxy: %w", err) } @@ -118,6 +122,7 @@ func (t *Tunnel) EnableSNIReporter(filename, suffix, country string) error { } func (t *Tunnel) Disconnect() { + t.cancel() t.Close() t.tun.Close() } From 49c62e45374e6baf6b128b728fc63ff4150d2852 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Tue, 20 Feb 2024 21:25:14 -0500 Subject: [PATCH 08/17] fix errors.Is nil reference panic --- Android/app/src/go/doh/doh.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Android/app/src/go/doh/doh.go b/Android/app/src/go/doh/doh.go index dac36b97..429f1869 100644 --- a/Android/app/src/go/doh/doh.go +++ b/Android/app/src/go/doh/doh.go @@ -442,6 +442,7 @@ func (t *transport) Query(ctx context.Context, q []byte) ([]byte, error) { response, server, qerr := t.doQuery(ctx, q) after := time.Now() + errIsCancel := false var err error status := Complete httpStatus := http.StatusOK @@ -449,6 +450,7 @@ func (t *transport) Query(ctx context.Context, q []byte) ([]byte, error) { err = qerr status = qerr.status httpStatus = 0 + errIsCancel = errors.Is(qerr, context.Canceled) var herr *httpError if errors.As(qerr.err, &herr) { @@ -461,14 +463,13 @@ func (t *transport) Query(ctx context.Context, q []byte) ([]byte, error) { // the following deadlock: // 1. Java - synchronized VpnController.stop() // 2. Go - context.Cancel() - // 3. Go - errors.Is(qerr, context.Cancelled) - // 4. Go - (if we don't stop sending OnResponse) - // 5. Java - GoIntraListener.onResponse - // 6. Java - synchronized VpnController.onConnectionStateChanged() + // 3. Go - (if we don't stop sending OnResponse) + // 4. Java - GoIntraListener.onResponse + // 5. Java - synchronized VpnController.onConnectionStateChanged() // Deadlock happens (both Step 1 and Step 6 are marked as synchronized)! // // TODO: make stop() an asynchronized function - if t.listener != nil && !errors.Is(qerr, context.Canceled) { + if t.listener != nil && !errIsCancel { latency := after.Sub(before) var ip string if server != nil { From 4ca205d79421ae7657d20cf5aef389e47b220c35 Mon Sep 17 00:00:00 2001 From: "J. Yi" <93548144+jyyi1@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:50:28 -0500 Subject: [PATCH 09/17] Update Android/app/src/go/doh/doh.go Co-authored-by: Vinicius Fortuna --- Android/app/src/go/doh/doh.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android/app/src/go/doh/doh.go b/Android/app/src/go/doh/doh.go index 429f1869..1a13e270 100644 --- a/Android/app/src/go/doh/doh.go +++ b/Android/app/src/go/doh/doh.go @@ -466,7 +466,7 @@ func (t *transport) Query(ctx context.Context, q []byte) ([]byte, error) { // 3. Go - (if we don't stop sending OnResponse) // 4. Java - GoIntraListener.onResponse // 5. Java - synchronized VpnController.onConnectionStateChanged() - // Deadlock happens (both Step 1 and Step 6 are marked as synchronized)! + // Deadlock happens (both Step 1 and Step 5 are marked as synchronized)! // // TODO: make stop() an asynchronized function if t.listener != nil && !errIsCancel { From b31ff6bfc7ff7b7509af8c2d573186b7dd520b19 Mon Sep 17 00:00:00 2001 From: "J. Yi" <93548144+jyyi1@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:51:08 -0500 Subject: [PATCH 10/17] Update Android/app/src/go/doh/doh_test.go Co-authored-by: Vinicius Fortuna --- Android/app/src/go/doh/doh_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Android/app/src/go/doh/doh_test.go b/Android/app/src/go/doh/doh_test.go index c930be1c..b1bf4a94 100644 --- a/Android/app/src/go/doh/doh_test.go +++ b/Android/app/src/go/doh/doh_test.go @@ -923,8 +923,8 @@ type testingDoHListener struct { func (l *testingDoHListener) OnQuery(url string) Token { return nil } func (l *testingDoHListener) OnResponse(tok Token, s *Summary) { l.summary = s } -func makeTestDoHTransport(t *testing.T, target testingDoHServer) Transport { - doh, err := NewTransport(target.url, target.ips, nil, nil, nil) +func newTestDOHResolver(t *testing.T, config dohConfig) DOHResolver { + doh, err := NewDOHResolver(target.url, target.ips, nil, nil, nil) require.NoError(t, err) require.NotNil(t, doh) return doh From a5ce3531eefed097e2c09047e49a3008a3053412 Mon Sep 17 00:00:00 2001 From: "J. Yi" <93548144+jyyi1@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:52:42 -0500 Subject: [PATCH 11/17] Update Android/app/src/main/java/app/intra/net/go/GoIntraListener.java Co-authored-by: Vinicius Fortuna --- Android/app/src/main/java/app/intra/net/go/GoIntraListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java b/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java index ab756235..352fb8a1 100644 --- a/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java +++ b/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java @@ -101,7 +101,7 @@ public void onUDPSocketClosed(UDPSocketSummary summary) { goStatusMap.put(Backend.DoHStatusInternalError, Status.INTERNAL_ERROR); } - // Wrapping HttpMetric into a doh.Token allows us to get paired query and response notifications + // Wrapping HttpMetric into a DoHQueryToken allows us to get paired query and response notifications // from Go without reverse-binding any Java APIs into Go. Pairing these notifications is // required by the structure of the HttpMetric API (which does not have any other way to record // latency), and reverse binding is worth avoiding, especially because it's not compatible with From c3a6b5fcd6332e5403e55eed0a8ac34a6717feca Mon Sep 17 00:00:00 2001 From: "J. Yi" <93548144+jyyi1@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:52:49 -0500 Subject: [PATCH 12/17] Update Android/app/src/main/java/app/intra/net/go/GoProber.java Co-authored-by: Vinicius Fortuna --- Android/app/src/main/java/app/intra/net/go/GoProber.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android/app/src/main/java/app/intra/net/go/GoProber.java b/Android/app/src/main/java/app/intra/net/go/GoProber.java index 033e9888..c2f4ea25 100644 --- a/Android/app/src/main/java/app/intra/net/go/GoProber.java +++ b/Android/app/src/main/java/app/intra/net/go/GoProber.java @@ -42,7 +42,7 @@ public void probe(String url, Callback callback) { // Protection isn't needed for Lollipop+, or if the VPN is not active. Protector protector = VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP ? null : VpnController.getInstance().getIntraVpnService(); - new DoHServer(url, dohIPs, protector, null).probe(); + Probe(new DoHServer(url, dohIPs, protector, null)); callback.onCompleted(true); } catch (Exception e) { callback.onCompleted(false); From 72ef2cd26fa8afb2401dee6c867c0e7e749f0ed2 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 21 Feb 2024 14:16:46 -0500 Subject: [PATCH 13/17] rename doh.Transport to doh.Resolver --- Android/app/src/go/backend/doh.go | 6 +- Android/app/src/go/backend/tunnel.go | 4 +- Android/app/src/go/doh/doh.go | 93 +++++++------- Android/app/src/go/doh/doh_test.go | 119 +++++++++--------- Android/app/src/go/intra/packet_proxy.go | 6 +- Android/app/src/go/intra/sni_reporter.go | 4 +- Android/app/src/go/intra/sni_reporter_test.go | 12 +- Android/app/src/go/intra/stream_dialer.go | 6 +- Android/app/src/go/intra/tunnel.go | 11 +- 9 files changed, 127 insertions(+), 134 deletions(-) diff --git a/Android/app/src/go/backend/doh.go b/Android/app/src/go/backend/doh.go index 83a9f46a..831719de 100644 --- a/Android/app/src/go/backend/doh.go +++ b/Android/app/src/go/backend/doh.go @@ -26,7 +26,7 @@ import ( // DoHServer represents a DNS-over-HTTPS server. type DoHServer struct { - tspt doh.Transport + r doh.Resolver } // NewDoHServer creates a DoHServer that connects to the specified DoH server. @@ -47,7 +47,7 @@ func NewDoHServer( ips = strings.Split(ipsStr, ",") } dialer := protect.MakeDialer(protector) - t, err := doh.NewTransport(url, ips, dialer, nil, makeInternalDoHListener(listener)) + t, err := doh.NewResolver(url, ips, dialer, nil, makeInternalDoHListener(listener)) if err != nil { return nil, err } @@ -75,7 +75,7 @@ var dohQuery = []byte{ // // If the server responds correctly, the function returns nil. Otherwise, the function returns an error. func (s *DoHServer) Probe() error { - resp, err := s.tspt.Query(context.Background(), dohQuery) + resp, err := s.r.Query(context.Background(), dohQuery) if err != nil { return fmt.Errorf("failed to send query: %w", err) } diff --git a/Android/app/src/go/backend/tunnel.go b/Android/app/src/go/backend/tunnel.go index f9814a2e..5c9058af 100644 --- a/Android/app/src/go/backend/tunnel.go +++ b/Android/app/src/go/backend/tunnel.go @@ -35,7 +35,7 @@ type Session struct { *intra.Tunnel } -func (s *Session) SetDoHServer(svr *DoHServer) { s.SetDNS(svr.tspt) } +func (s *Session) SetDoHServer(svr *DoHServer) { s.SetDNS(svr.r) } // ConnectSession reads packets from a TUN device and applies the Intra routing // rules. Currently, this only consists of redirecting DNS packets to a specified @@ -64,7 +64,7 @@ func ConnectSession( if dohdns == nil { return nil, errors.New("dohdns must not be nil") } - t, err := intra.NewTunnel(fakedns, dohdns.tspt, tun, protector, listener) + t, err := intra.NewTunnel(fakedns, dohdns.r, tun, protector, listener) if err != nil { return nil, err } diff --git a/Android/app/src/go/doh/doh.go b/Android/app/src/go/doh/doh.go index 1a13e270..71686267 100644 --- a/Android/app/src/go/doh/doh.go +++ b/Android/app/src/go/doh/doh.go @@ -78,22 +78,22 @@ type Listener interface { OnResponse(Token, *Summary) } -// Transport represents a DNS query transport. -type Transport interface { - // Query sends a DNS query represented by q (including ID) to this DoH server +// Resolver represents a DNS-over-HTTPS (DoH) resolver. +type Resolver interface { + // Query sends a DNS query represented by q (including ID) to this DoH resolver // (located at GetURL) using the provided context, and returns the correponding // - // A non-nil error will be returned if no response was received from the DoH server, + // A non-nil error will be returned if no response was received from the DoH resolver, // the error may also be accompanied by a SERVFAIL response if appropriate. Query(ctx context.Context, q []byte) ([]byte, error) - // Return the server URL used to initialize this transport. + // Return the server URL used to initialize this DoH resolver. GetURL() string } // TODO: Keep a context here so that queries can be canceled. -type transport struct { - Transport +type resolver struct { + Resolver url string hostname string port int @@ -108,7 +108,7 @@ type transport struct { // Wait up to three seconds for the TCP handshake to complete. const tcpTimeout time.Duration = 3 * time.Second -func (t *transport) dial(ctx context.Context, network, addr string) (net.Conn, error) { +func (r *resolver) dial(ctx context.Context, network, addr string) (net.Conn, error) { logging.Debug.Printf("Dialing %s\n", addr) domain, portStr, err := net.SplitHostPort(addr) if err != nil { @@ -125,11 +125,11 @@ func (t *transport) dial(ctx context.Context, network, addr string) (net.Conn, e // TODO: Improve IP fallback strategy with parallelism and Happy Eyeballs. var conn net.Conn - ips := t.ips.Get(domain) + ips := r.ips.Get(domain) confirmed := ips.Confirmed() if confirmed != nil { logging.Debug.Printf("Trying confirmed IP %s for addr %s\n", confirmed.String(), addr) - if conn, err = split.DialWithSplitRetry(ctx, t.dialer, tcpaddr(confirmed), nil); err == nil { + if conn, err = split.DialWithSplitRetry(ctx, r.dialer, tcpaddr(confirmed), nil); err == nil { logging.Info.Printf("Confirmed IP %s worked\n", confirmed.String()) return conn, nil } @@ -143,7 +143,7 @@ func (t *transport) dial(ctx context.Context, network, addr string) (net.Conn, e // Don't try this IP twice. continue } - if conn, err = split.DialWithSplitRetry(ctx, t.dialer, tcpaddr(ip), nil); err == nil { + if conn, err = split.DialWithSplitRetry(ctx, r.dialer, tcpaddr(ip), nil); err == nil { logging.Info.Printf("Found working IP: %s\n", ip.String()) return conn, nil } @@ -151,7 +151,7 @@ func (t *transport) dial(ctx context.Context, network, addr string) (net.Conn, e return nil, err } -// NewTransport returns a DoH DNSTransport, ready for use. +// NewResolver returns a DoH [Resolver], ready for use. // This is a POST-only DoH implementation, so the DoH template should be a URL. // // `rawurl` is the DoH template in string form. @@ -159,13 +159,13 @@ func (t *transport) dial(ctx context.Context, network, addr string) (net.Conn, e // `addrs` is a list of domains or IP addresses to use as fallback, if the hostname lookup fails or // returns non-working addresses. // -// `dialer` is the dialer that the transport will use. The transport will modify the dialer's +// `dialer` is the dialer that the [Resolver] will use. The [Resolver] will modify the dialer's // timeout but will not mutate it otherwise. // // `auth` will provide a client certificate if required by the TLS server. // // `listener` will receive the status of each DNS query when it is complete. -func NewTransport(rawurl string, addrs []string, dialer *net.Dialer, auth ClientAuth, listener Listener) (Transport, error) { +func NewResolver(rawurl string, addrs []string, dialer *net.Dialer, auth ClientAuth, listener Listener) (Resolver, error) { if dialer == nil { dialer = &net.Dialer{} } @@ -188,7 +188,7 @@ func NewTransport(rawurl string, addrs []string, dialer *net.Dialer, auth Client port = 443 } - t := &transport{ + t := &resolver{ url: rawurl, hostname: parsedurl.Hostname(), port: port, @@ -251,15 +251,15 @@ func (e *httpError) Error() string { // Independent of the query's success or failure, this function also returns the // address of the server on a best-effort basis, or nil if the address could not // be determined. -func (t *transport) doQuery(ctx context.Context, q []byte) (response []byte, server *net.TCPAddr, qerr *queryError) { +func (r *resolver) doQuery(ctx context.Context, q []byte) (response []byte, server *net.TCPAddr, qerr *queryError) { if len(q) < 2 { qerr = &queryError{BadQuery, fmt.Errorf("Query length is %d", len(q))} return } - t.hangoverLock.RLock() - inHangover := time.Now().Before(t.hangoverExpiration) - t.hangoverLock.RUnlock() + r.hangoverLock.RLock() + inHangover := time.Now().Before(r.hangoverExpiration) + r.hangoverLock.RUnlock() if inHangover { response = tryServfail(q) qerr = &queryError{HTTPError, errors.New("Forwarder is in servfail hangover")} @@ -276,14 +276,14 @@ func (t *transport) doQuery(ctx context.Context, q []byte) (response []byte, ser // Zero out the query ID. id := binary.BigEndian.Uint16(q) binary.BigEndian.PutUint16(q, 0) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, t.url, bytes.NewBuffer(q)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.url, bytes.NewBuffer(q)) if err != nil { qerr = &queryError{InternalError, err} return } var hostname string - response, hostname, server, qerr = t.sendRequest(id, req) + response, hostname, server, qerr = r.sendRequest(id, req) // Restore the query ID. binary.BigEndian.PutUint16(q, id) @@ -301,21 +301,21 @@ func (t *transport) doQuery(ctx context.Context, q []byte) (response []byte, ser if qerr != nil { if qerr.status != SendFailed { - t.hangoverLock.Lock() - t.hangoverExpiration = time.Now().Add(hangoverDuration) - t.hangoverLock.Unlock() + r.hangoverLock.Lock() + r.hangoverExpiration = time.Now().Add(hangoverDuration) + r.hangoverLock.Unlock() } response = tryServfail(q) } else if server != nil { // Record a working IP address for this server iff qerr is nil - t.ips.Get(hostname).Confirm(server.IP) + r.ips.Get(hostname).Confirm(server.IP) } return } -func (t *transport) sendRequest(id uint16, req *http.Request) (response []byte, hostname string, server *net.TCPAddr, qerr *queryError) { - hostname = t.hostname +func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, hostname string, server *net.TCPAddr, qerr *queryError) { + hostname = r.hostname // The connection used for this request. If the request fails, we will close // this socket, in case it is no longer functioning. @@ -331,7 +331,7 @@ func (t *transport) sendRequest(id uint16, req *http.Request) (response []byte, logging.Info.Printf("%d Query failed: %v\n", id, qerr) if server != nil { logging.Debug.Printf("%d Disconfirming %s\n", id, server.IP.String()) - t.ips.Get(hostname).Disconfirm(server.IP) + r.ips.Get(hostname).Disconfirm(server.IP) } if conn != nil { logging.Info.Printf("%d Closing failing DoH socket\n", id) @@ -401,7 +401,7 @@ func (t *transport) sendRequest(id uint16, req *http.Request) (response []byte, req.Header.Set("Accept", mimetype) req.Header.Set("User-Agent", "Intra") logging.Debug.Printf("%d Sending query\n", id) - httpResponse, err := t.client.Do(req) + httpResponse, err := r.client.Do(req) if err != nil { qerr = &queryError{SendFailed, err} return @@ -432,14 +432,14 @@ func (t *transport) sendRequest(id uint16, req *http.Request) (response []byte, return } -func (t *transport) Query(ctx context.Context, q []byte) ([]byte, error) { +func (r *resolver) Query(ctx context.Context, q []byte) ([]byte, error) { var token Token - if t.listener != nil { - token = t.listener.OnQuery(t.url) + if r.listener != nil { + token = r.listener.OnQuery(r.url) } before := time.Now() - response, server, qerr := t.doQuery(ctx, q) + response, server, qerr := r.doQuery(ctx, q) after := time.Now() errIsCancel := false @@ -469,14 +469,14 @@ func (t *transport) Query(ctx context.Context, q []byte) ([]byte, error) { // Deadlock happens (both Step 1 and Step 5 are marked as synchronized)! // // TODO: make stop() an asynchronized function - if t.listener != nil && !errIsCancel { + if r.listener != nil && !errIsCancel { latency := after.Sub(before) var ip string if server != nil { ip = server.IP.String() } - t.listener.OnResponse(token, &Summary{ + r.listener.OnResponse(token, &Summary{ Latency: latency.Seconds(), Query: q, Response: response, @@ -488,13 +488,13 @@ func (t *transport) Query(ctx context.Context, q []byte) ([]byte, error) { return response, err } -func (t *transport) GetURL() string { - return t.url +func (r *resolver) GetURL() string { + return r.url } -// Perform a query using the transport, and send the response to the writer. -func forwardQuery(t Transport, q []byte, c io.Writer) error { - resp, qerr := t.Query(context.Background(), q) +// Perform a query using the Resolver, and send the response to the writer. +func forwardQuery(r Resolver, q []byte, c io.Writer) error { + resp, qerr := r.Query(context.Background(), q) if resp == nil && qerr != nil { return qerr } @@ -517,18 +517,17 @@ func forwardQuery(t Transport, q []byte, c io.Writer) error { return qerr } -// Perform a query using the transport, send the response to the writer, +// Perform a query using the Resolver, send the response to the writer, // and close the writer if there was an error. -func forwardQueryAndCheck(t Transport, q []byte, c io.WriteCloser) { - if err := forwardQuery(t, q, c); err != nil { +func forwardQueryAndCheck(r Resolver, q []byte, c io.WriteCloser) { + if err := forwardQuery(r, q, c); err != nil { logging.Warn.Printf("Query forwarding failed: %v\n", err) c.Close() } } -// Accept a DNS-over-TCP socket from a stub resolver, and connect the socket -// to this DNSTransport. -func Accept(t Transport, c io.ReadWriteCloser) { +// Accept a DNS-over-TCP socket, and connect the socket to a DoH Resolver. +func Accept(r Resolver, c io.ReadWriteCloser) { qlbuf := make([]byte, 2) for { n, err := c.Read(qlbuf) @@ -555,7 +554,7 @@ func Accept(t Transport, c io.ReadWriteCloser) { logging.Warn.Printf("Incomplete query: %d < %d\n", n, qlen) break } - go forwardQueryAndCheck(t, q, c) + go forwardQueryAndCheck(r, q, c) } // TODO: Cancel outstanding queries at this point. c.Close() diff --git a/Android/app/src/go/doh/doh_test.go b/Android/app/src/go/doh/doh_test.go index b1bf4a94..5d5164c5 100644 --- a/Android/app/src/go/doh/doh_test.go +++ b/Android/app/src/go/doh/doh_test.go @@ -122,17 +122,17 @@ func init() { } // Check that the constructor works. -func TestNewTransport(t *testing.T) { - makeTestDoHTransport(t, googleDoH) +func TestNewResolver(t *testing.T) { + newTestDoHResolver(t, googleDoH) } // Check that the constructor rejects unsupported URLs. func TestBadUrl(t *testing.T) { - _, err := NewTransport("ftp://www.example.com", nil, nil, nil, nil) + _, err := NewResolver("ftp://www.example.com", nil, nil, nil, nil) if err == nil { t.Error("Expected error") } - _, err = NewTransport("https://www.example", nil, nil, nil, nil) + _, err = NewResolver("https://www.example", nil, nil, nil, nil) if err == nil { t.Error("Expected error") } @@ -141,7 +141,7 @@ func TestBadUrl(t *testing.T) { // Check for failure when the query is too short to be valid. func TestShortQuery(t *testing.T) { var qerr *queryError - doh := makeTestDoHTransport(t, googleDoH) + doh := newTestDoHResolver(t, googleDoH) _, err := doh.Query(context.Background(), []byte{}) if err == nil { t.Error("Empty query should fail") @@ -179,7 +179,7 @@ func TestQueryIntegration(t *testing.T) { } testQuery := func(queryData []byte) { - doh := makeTestDoHTransport(t, googleDoH) + doh := newTestDoHResolver(t, googleDoH) resp, err2 := doh.Query(context.Background(), queryData) if err2 != nil { t.Fatal(err2) @@ -226,11 +226,10 @@ func (r *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) // Check that a DNS query is converted correctly into an HTTP query. func TestRequest(t *testing.T) { - doh := makeTestDoHTransport(t, googleDoH) - transport := doh.(*transport) + resolver := newTestDoHResolver(t, googleDoH) rt := makeTestRoundTripper() - transport.client.Transport = rt - go doh.Query(context.Background(), simpleQueryBytes) + resolver.client.Transport = rt + go resolver.Query(context.Background(), simpleQueryBytes) req := <-rt.req if req.URL.String() != googleDoH.url { t.Errorf("URL mismatch: %s != %s", req.URL.String(), googleDoH.url) @@ -275,10 +274,9 @@ func queriesMostlyEqual(m1 dnsmessage.Message, m2 dnsmessage.Message) bool { // Check that a DOH response is returned correctly. func TestResponse(t *testing.T) { - doh := makeTestDoHTransport(t, googleDoH) - transport := doh.(*transport) + resolver := newTestDoHResolver(t, googleDoH) rt := makeTestRoundTripper() - transport.client.Transport = rt + resolver.client.Transport = rt // Fake server. go func() { @@ -296,7 +294,7 @@ func TestResponse(t *testing.T) { w.Close() }() - resp, err := doh.Query(context.Background(), simpleQueryBytes) + resp, err := resolver.Query(context.Background(), simpleQueryBytes) if err != nil { t.Error(err) } @@ -314,10 +312,9 @@ func TestResponse(t *testing.T) { // Simulate an empty response. (This is not a compliant server // behavior.) func TestEmptyResponse(t *testing.T) { - doh := makeTestDoHTransport(t, googleDoH) - transport := doh.(*transport) + resolver := newTestDoHResolver(t, googleDoH) rt := makeTestRoundTripper() - transport.client.Transport = rt + resolver.client.Transport = rt // Fake server. go func() { @@ -332,7 +329,7 @@ func TestEmptyResponse(t *testing.T) { } }() - _, err := doh.Query(context.Background(), simpleQueryBytes) + _, err := resolver.Query(context.Background(), simpleQueryBytes) var qerr *queryError if err == nil { t.Error("Empty body should cause an error") @@ -345,10 +342,9 @@ func TestEmptyResponse(t *testing.T) { // Simulate a non-200 HTTP response code. func TestHTTPError(t *testing.T) { - doh := makeTestDoHTransport(t, googleDoH) - transport := doh.(*transport) + resolver := newTestDoHResolver(t, googleDoH) rt := makeTestRoundTripper() - transport.client.Transport = rt + resolver.client.Transport = rt go func() { <-rt.req @@ -362,7 +358,7 @@ func TestHTTPError(t *testing.T) { w.Close() }() - _, err := doh.Query(context.Background(), simpleQueryBytes) + _, err := resolver.Query(context.Background(), simpleQueryBytes) var qerr *queryError if err == nil { t.Error("Empty body should cause an error") @@ -375,13 +371,12 @@ func TestHTTPError(t *testing.T) { // Simulate an HTTP query error. func TestSendFailed(t *testing.T) { - doh := makeTestDoHTransport(t, googleDoH) - transport := doh.(*transport) + resolver := newTestDoHResolver(t, googleDoH) rt := makeTestRoundTripper() - transport.client.Transport = rt + resolver.client.Transport = rt rt.err = errors.New("test") - _, err := doh.Query(context.Background(), simpleQueryBytes) + _, err := resolver.Query(context.Background(), simpleQueryBytes) var qerr *queryError if err == nil { t.Error("Send failure should be reported") @@ -398,13 +393,12 @@ func TestSendFailed(t *testing.T) { // when queries suceeded and fail, respectively. func TestDohIPConfirmDisconfirm(t *testing.T) { u, _ := url.Parse(googleDoH.url) - doh := makeTestDoHTransport(t, googleDoH) - transport := doh.(*transport) + resolver := newTestDoHResolver(t, googleDoH) hostname := u.Hostname() - ipmap := transport.ips.Get(hostname) + ipmap := resolver.ips.Get(hostname) // send a valid request to first have confirmed-ip set - res, _ := doh.Query(context.Background(), simpleQueryBytes) + res, _ := resolver.Query(context.Background(), simpleQueryBytes) mustUnpack(res) ip1 := ipmap.Confirmed() @@ -414,7 +408,7 @@ func TestDohIPConfirmDisconfirm(t *testing.T) { // simulate http-fail with doh server-ip set to previously confirmed-ip rt := makeTestRoundTripper() - transport.client.Transport = rt + resolver.client.Transport = rt go func() { req := <-rt.req trace := httptrace.ContextClientTrace(req.Context()) @@ -430,7 +424,7 @@ func TestDohIPConfirmDisconfirm(t *testing.T) { Request: &http.Request{URL: u}, } }() - doh.Query(context.Background(), simpleQueryBytes) + resolver.Query(context.Background(), simpleQueryBytes) ip2 := ipmap.Confirmed() if ip2 != nil { @@ -449,10 +443,9 @@ func (c *fakeConn) RemoteAddr() net.Addr { // Check that the DNSListener is called with a correct summary. func TestListener(t *testing.T) { - doh, listener := makeTestDoHTransportWithListener(t, googleDoH) - transport := doh.(*transport) + resolver, listener := newTestDoHResolverWithListener(t, googleDoH) rt := makeTestRoundTripper() - transport.client.Transport = rt + resolver.client.Transport = rt go func() { req := <-rt.req @@ -474,7 +467,7 @@ func TestListener(t *testing.T) { w.Close() }() - doh.Query(context.Background(), simpleQueryBytes) + resolver.Query(context.Background(), simpleQueryBytes) s := listener.summary if s.Latency < 0 { t.Errorf("Negative latency: %f", s.Latency) @@ -521,14 +514,14 @@ func makePair() (io.ReadWriteCloser, io.ReadWriteCloser) { return &socket{r1, w2}, &socket{r2, w1} } -type fakeTransport struct { - Transport +type fakeResolver struct { + Resolver query chan []byte response chan []byte err error } -func (t *fakeTransport) Query(ctx context.Context, q []byte) ([]byte, error) { +func (t *fakeResolver) Query(ctx context.Context, q []byte) ([]byte, error) { t.query <- q if t.err != nil { return nil, t.err @@ -536,18 +529,18 @@ func (t *fakeTransport) Query(ctx context.Context, q []byte) ([]byte, error) { return <-t.response, nil } -func (t *fakeTransport) GetURL() string { +func (t *fakeResolver) GetURL() string { return "fake" } -func (t *fakeTransport) Close() { +func (t *fakeResolver) Close() { t.err = errors.New("closed") close(t.query) close(t.response) } -func newFakeTransport() *fakeTransport { - return &fakeTransport{ +func newFakeResolver() *fakeResolver { + return &fakeResolver{ query: make(chan []byte), response: make(chan []byte), } @@ -555,7 +548,7 @@ func newFakeTransport() *fakeTransport { // Test a successful query over TCP func TestAccept(t *testing.T) { - doh := newFakeTransport() + doh := newFakeResolver() client, server := makePair() // Start the forwarder running. @@ -614,7 +607,7 @@ func TestAccept(t *testing.T) { // Sends a TCP query that results in failure. When a query fails, // Accept should close the TCP socket. func TestAcceptFail(t *testing.T) { - doh := newFakeTransport() + doh := newFakeResolver() client, server := makePair() // Start the forwarder running. @@ -646,7 +639,7 @@ func TestAcceptFail(t *testing.T) { // Sends a TCP query, and closes the socket before the response is sent. // This tests for crashes when a response cannot be delivered. func TestAcceptClose(t *testing.T) { - doh := newFakeTransport() + doh := newFakeResolver() client, server := makePair() // Start the forwarder running. @@ -676,7 +669,7 @@ func TestAcceptClose(t *testing.T) { // Test failure due to a response that is larger than the // maximum message size for DNS over TCP (65535). func TestAcceptOversize(t *testing.T) { - doh := newFakeTransport() + doh := newFakeResolver() client, server := makePair() // Start the forwarder running. @@ -865,16 +858,16 @@ func TestServfail(t *testing.T) { } func TestQueryCanBeCancelled(t *testing.T) { - expectDoHTimeout := func(server testingDoHServer, msg string) { - doh := makeTestDoHTransport(t, server) + expectDoHTimeout := func(config testingDoHConfig, msg string) { + doh := newTestDoHResolver(t, config) st := time.Now() ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() doh.Query(ctx, simpleQueryBytes) - require.WithinRange(t, time.Now(), st.Add(1700*time.Millisecond), st.Add(2300*time.Millisecond), msg) + require.WithinRange(t, time.Now(), st.Add(1500*time.Millisecond), st.Add(2500*time.Millisecond), msg) } - expectDoHTimeout(unreachableDoH, "unreachable server should timeout within deadline of ctx") + expectDoHTimeout(unreachableDoH, "unreachable resolver should timeout within deadline of ctx") // Intentionally create a local DoH server that does not accept any requests addr, err := url.Parse(localDoH.url) @@ -885,21 +878,21 @@ func TestQueryCanBeCancelled(t *testing.T) { require.NoError(t, err) defer svr.Close() - expectDoHTimeout(localDoH, "unresponsive server should timeout within deadline of ctx") + expectDoHTimeout(localDoH, "unresponsive resolver should timeout within deadline of ctx") } /******** Test DoH servers ********/ -type testingDoHServer struct { +type testingDoHConfig struct { url string ips []string } -var unreachableDoH = testingDoHServer{ +var unreachableDoH = testingDoHConfig{ url: "https://1.2.3.4:443", ips: []string{"1.2.3.4"}, } -var googleDoH = testingDoHServer{ +var googleDoH = testingDoHConfig{ url: "https://dns.google/dns-query", ips: []string{ "8.8.8.8", @@ -909,12 +902,12 @@ var googleDoH = testingDoHServer{ }, } -var localDoH = testingDoHServer{ +var localDoH = testingDoHConfig{ url: "https://localhost:34443", ips: []string{"127.0.0.1"}, } -/********** DoH Transport Test Helpers **********/ +/********** DoH Resolver Test Helpers **********/ type testingDoHListener struct { Listener summary *Summary @@ -923,17 +916,17 @@ type testingDoHListener struct { func (l *testingDoHListener) OnQuery(url string) Token { return nil } func (l *testingDoHListener) OnResponse(tok Token, s *Summary) { l.summary = s } -func newTestDOHResolver(t *testing.T, config dohConfig) DOHResolver { - doh, err := NewDOHResolver(target.url, target.ips, nil, nil, nil) +func newTestDoHResolver(t *testing.T, config testingDoHConfig) *resolver { + doh, err := NewResolver(config.url, config.ips, nil, nil, nil) require.NoError(t, err) require.NotNil(t, doh) - return doh + return doh.(*resolver) } -func makeTestDoHTransportWithListener(t *testing.T, target testingDoHServer) (Transport, *testingDoHListener) { +func newTestDoHResolverWithListener(t *testing.T, config testingDoHConfig) (*resolver, *testingDoHListener) { listener := &testingDoHListener{} - doh, err := NewTransport(target.url, target.ips, nil, nil, listener) + doh, err := NewResolver(config.url, config.ips, nil, nil, listener) require.NoError(t, err) require.NotNil(t, doh) - return doh, listener + return doh.(*resolver), listener } diff --git a/Android/app/src/go/intra/packet_proxy.go b/Android/app/src/go/intra/packet_proxy.go index 1bd28481..fecb8f48 100644 --- a/Android/app/src/go/intra/packet_proxy.go +++ b/Android/app/src/go/intra/packet_proxy.go @@ -32,7 +32,7 @@ import ( type intraPacketProxy struct { fakeDNSAddr netip.AddrPort - dns atomic.Pointer[doh.Transport] + dns atomic.Pointer[doh.Resolver] proxy network.PacketProxy listener UDPListener ctx context.Context @@ -41,7 +41,7 @@ type intraPacketProxy struct { var _ network.PacketProxy = (*intraPacketProxy)(nil) func newIntraPacketProxy( - ctx context.Context, fakeDNS netip.AddrPort, dns doh.Transport, protector protect.Protector, listener UDPListener, + ctx context.Context, fakeDNS netip.AddrPort, dns doh.Resolver, protector protect.Protector, listener UDPListener, ) (*intraPacketProxy, error) { if dns == nil { return nil, errors.New("dns is required") @@ -88,7 +88,7 @@ func (p *intraPacketProxy) NewSession(resp network.PacketResponseReceiver) (netw }, nil } -func (p *intraPacketProxy) SetDNS(dns doh.Transport) error { +func (p *intraPacketProxy) SetDNS(dns doh.Resolver) error { if dns == nil { return errors.New("dns is required") } diff --git a/Android/app/src/go/intra/sni_reporter.go b/Android/app/src/go/intra/sni_reporter.go index 9472fc34..07353fcf 100644 --- a/Android/app/src/go/intra/sni_reporter.go +++ b/Android/app/src/go/intra/sni_reporter.go @@ -42,13 +42,13 @@ const burst = 10 * time.Second // tcpSNIReporter is a thread-safe wrapper around choir.Reporter type tcpSNIReporter struct { mu sync.RWMutex // Protects dns, suffix, and r. - dns doh.Transport + dns doh.Resolver suffix string r choir.Reporter } // SetDNS changes the DNS transport used for uploading reports. -func (r *tcpSNIReporter) SetDNS(dns doh.Transport) { +func (r *tcpSNIReporter) SetDNS(dns doh.Resolver) { r.mu.Lock() r.dns = dns r.mu.Unlock() diff --git a/Android/app/src/go/intra/sni_reporter_test.go b/Android/app/src/go/intra/sni_reporter_test.go index 0e878a7a..c89122f1 100644 --- a/Android/app/src/go/intra/sni_reporter_test.go +++ b/Android/app/src/go/intra/sni_reporter_test.go @@ -29,17 +29,17 @@ import ( type qfunc func(q []byte) ([]byte, error) -type fakeTransport struct { - doh.Transport +type fakeResolver struct { + doh.Resolver query qfunc } -func (t *fakeTransport) Query(ctx context.Context, q []byte) ([]byte, error) { - return t.query(q) +func (r *fakeResolver) Query(ctx context.Context, q []byte) ([]byte, error) { + return r.query(q) } -func newFakeTransport(query qfunc) *fakeTransport { - return &fakeTransport{query: query} +func newFakeTransport(query qfunc) *fakeResolver { + return &fakeResolver{query: query} } func sendReport(t *testing.T, r *tcpSNIReporter, summary TCPSocketSummary, response []byte, responseErr error) string { diff --git a/Android/app/src/go/intra/stream_dialer.go b/Android/app/src/go/intra/stream_dialer.go index 68d8c7a3..0af65b8a 100644 --- a/Android/app/src/go/intra/stream_dialer.go +++ b/Android/app/src/go/intra/stream_dialer.go @@ -32,7 +32,7 @@ import ( type intraStreamDialer struct { fakeDNSAddr netip.AddrPort - dns atomic.Pointer[doh.Transport] + dns atomic.Pointer[doh.Resolver] dialer *net.Dialer alwaysSplitHTTPS atomic.Bool listener TCPListener @@ -43,7 +43,7 @@ var _ transport.StreamDialer = (*intraStreamDialer)(nil) func newIntraStreamDialer( fakeDNS netip.AddrPort, - dns doh.Transport, + dns doh.Resolver, protector protect.Protector, listener TCPListener, sniReporter *tcpSNIReporter, @@ -86,7 +86,7 @@ func (sd *intraStreamDialer) Dial(ctx context.Context, raddr string) (transport. return makeTCPWrapConn(conn, stats, sd.listener, sd.sniReporter), nil } -func (sd *intraStreamDialer) SetDNS(dns doh.Transport) error { +func (sd *intraStreamDialer) SetDNS(dns doh.Resolver) error { if dns == nil { return errors.New("dns is required") } diff --git a/Android/app/src/go/intra/tunnel.go b/Android/app/src/go/intra/tunnel.go index eccb279e..a8098dec 100644 --- a/Android/app/src/go/intra/tunnel.go +++ b/Android/app/src/go/intra/tunnel.go @@ -59,10 +59,11 @@ type Tunnel struct { // // These will normally be localhost with a high-numbered port. // -// `dohdns` is the initial DOH transport. +// `dohdns` is the initial [Resolver]. +// // `eventListener` will be notified at the completion of every tunneled socket. func NewTunnel( - fakedns string, dohdns doh.Transport, tun io.Closer, protector protect.Protector, eventListener Listener, + fakedns string, dohdns doh.Resolver, tun io.Closer, protector protect.Protector, eventListener Listener, ) (t *Tunnel, err error) { if eventListener == nil { return nil, errors.New("eventListener is required") @@ -99,10 +100,10 @@ func NewTunnel( return } -// Set the DNSTransport. This method must be called before connecting the transport -// to the TUN device. The transport can be changed at any time during operation, but +// Set the DNS Resolver. This method must be called before connecting the transport +// to the TUN device. The transport can be changed at any time during operation, but // must not be nil. -func (t *Tunnel) SetDNS(dns doh.Transport) { +func (t *Tunnel) SetDNS(dns doh.Resolver) { t.sd.SetDNS(dns) t.pp.SetDNS(dns) t.sni.SetDNS(dns) From 3b117e4ad2a0f927262597ebbebd9e89a90e6619 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 21 Feb 2024 14:22:15 -0500 Subject: [PATCH 14/17] make Probe a global function instead of DoHServer's --- Android/app/src/go/backend/doh.go | 4 ++-- Android/app/src/go/doh/doh.go | 2 +- .../main/java/app/intra/net/go/GoProber.java | 3 ++- .../java/app/intra/net/go/GoVpnAdapter.java | 20 +++++++++---------- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Android/app/src/go/backend/doh.go b/Android/app/src/go/backend/doh.go index 831719de..6334e2bd 100644 --- a/Android/app/src/go/backend/doh.go +++ b/Android/app/src/go/backend/doh.go @@ -71,10 +71,10 @@ var dohQuery = []byte{ 0, 1, // QCLASS = IN (Internet) } -// Probe checks if this server can handle DNS-over-HTTPS (DoH) requests. +// Probe checks whether the [DoHServer] server can handle DNS-over-HTTPS (DoH) requests. // // If the server responds correctly, the function returns nil. Otherwise, the function returns an error. -func (s *DoHServer) Probe() error { +func Probe(s *DoHServer) error { resp, err := s.r.Query(context.Background(), dohQuery) if err != nil { return fmt.Errorf("failed to send query: %w", err) diff --git a/Android/app/src/go/doh/doh.go b/Android/app/src/go/doh/doh.go index 71686267..cb7abf19 100644 --- a/Android/app/src/go/doh/doh.go +++ b/Android/app/src/go/doh/doh.go @@ -70,7 +70,7 @@ type Summary struct { } // A Token is an opaque handle used to match responses to queries. -type Token = interface{} +type Token interface{} // Listener receives Summaries. type Listener interface { diff --git a/Android/app/src/main/java/app/intra/net/go/GoProber.java b/Android/app/src/main/java/app/intra/net/go/GoProber.java index c2f4ea25..261dcba1 100644 --- a/Android/app/src/main/java/app/intra/net/go/GoProber.java +++ b/Android/app/src/main/java/app/intra/net/go/GoProber.java @@ -20,6 +20,7 @@ import android.os.Build.VERSION_CODES; import app.intra.net.doh.Prober; import app.intra.sys.VpnController; +import backend.Backend; import backend.DoHServer; import protect.Protector; @@ -42,7 +43,7 @@ public void probe(String url, Callback callback) { // Protection isn't needed for Lollipop+, or if the VPN is not active. Protector protector = VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP ? null : VpnController.getInstance().getIntraVpnService(); - Probe(new DoHServer(url, dohIPs, protector, null)); + Backend.probe(new DoHServer(url, dohIPs, protector, null)); callback.onCompleted(true); } catch (Exception e) { callback.onCompleted(false); diff --git a/Android/app/src/main/java/app/intra/net/go/GoVpnAdapter.java b/Android/app/src/main/java/app/intra/net/go/GoVpnAdapter.java index 29c0220a..6b0d379e 100644 --- a/Android/app/src/main/java/app/intra/net/go/GoVpnAdapter.java +++ b/Android/app/src/main/java/app/intra/net/go/GoVpnAdapter.java @@ -88,7 +88,7 @@ String make(String template) { private ParcelFileDescriptor tunFd; // The Intra session object from go-tun2socks. Initially null. - private Session tunnel; + private Session session; private GoIntraListener listener; public static GoVpnAdapter establish(@NonNull IntraVpnService vpnService) { @@ -109,7 +109,7 @@ public synchronized void start() { } private void connectTunnel() { - if (tunnel != null) { + if (session != null) { return; } // VPN parameters @@ -123,7 +123,7 @@ private void connectTunnel() { LogWrapper.log(Log.INFO, LOG_TAG, "Starting go-tun2socks"); DoHServer server = makeDoHServer(dohURL); // connectIntraTunnel makes a copy of the file descriptor. - tunnel = Backend.connectSession(tunFd.getFd(), fakeDns, + session = Backend.connectSession(tunFd.getFd(), fakeDns, server, getProtector(), listener); } catch (Exception e) { LogWrapper.logException(e); @@ -150,7 +150,7 @@ private void enableChoir() { } String file = vpnService.getFilesDir() + File.separator + CHOIR_FILENAME; try { - tunnel.enableSNIReporter(file, "intra.metrics.gstatic.com", country); + session.enableSNIReporter(file, "intra.metrics.gstatic.com", country); } catch (Exception e) { // Choir setup failure is logged but otherwise ignored, because it does not prevent Intra // from functioning correctly. @@ -189,8 +189,8 @@ private static ParcelFileDescriptor establishVpn(IntraVpnService vpnService) { } public synchronized void close() { - if (tunnel != null) { - tunnel.disconnect(); + if (session != null) { + session.disconnect(); } if (tunFd != null) { try { @@ -229,7 +229,7 @@ public synchronized void updateDohUrl() { // Adapter is closed. return; } - if (tunnel == null) { + if (session == null) { // Attempt to re-create the tunnel. Creation may have failed originally because the DoH // server could not be reached. This will update the DoH URL as well. connectTunnel(); @@ -241,11 +241,11 @@ public synchronized void updateDohUrl() { // out. String url = PersistentState.getServerUrl(vpnService); try { - tunnel.setDoHServer(makeDoHServer(url)); + session.setDoHServer(makeDoHServer(url)); } catch (Exception e) { LogWrapper.logException(e); - tunnel.disconnect(); - tunnel = null; + session.disconnect(); + session = null; VpnController.getInstance().onConnectionStateChanged(vpnService, IntraVpnService.State.FAILING); } } From f95dd869db08f6ca9bded7c1afda1ac608689490 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 21 Feb 2024 14:29:10 -0500 Subject: [PATCH 15/17] DoHQueryStats -> DoHQuerySummary --- Android/app/src/go/backend/doh.go | 20 ++++++++-------- .../app/intra/net/go/GoIntraListener.java | 24 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Android/app/src/go/backend/doh.go b/Android/app/src/go/backend/doh.go index 6334e2bd..67366898 100644 --- a/Android/app/src/go/backend/doh.go +++ b/Android/app/src/go/backend/doh.go @@ -101,7 +101,7 @@ type DoHListener interface { OnQuery(url string) DoHQueryToken // OnResponse will be called when a DoH response has been received. - OnResponse(DoHQueryToken, *DoHQueryStats) + OnResponse(DoHQueryToken, *DoHQuerySumary) } // DoHStatus is an integer representing the status of a DoH transaction. @@ -116,18 +116,18 @@ const ( DoHStatusInternalError DoHStatus = doh.InternalError // This should never happen ) -// DoHQueryStats is the summary of a DNS transaction. +// DoHQuerySumary is the summary of a DNS transaction. // It will be reported to [DoHListener].OnResponse when it is complete. -type DoHQueryStats struct { +type DoHQuerySumary struct { summ *doh.Summary } -func (q DoHQueryStats) GetQuery() []byte { return q.summ.Query } -func (q DoHQueryStats) GetResponse() []byte { return q.summ.Response } -func (q DoHQueryStats) GetServer() string { return q.summ.Server } -func (q DoHQueryStats) GetStatus() DoHStatus { return q.summ.Status } -func (q DoHQueryStats) GetHTTPStatus() int { return q.summ.HTTPStatus } -func (q DoHQueryStats) GetLatency() float64 { return q.summ.Latency } +func (q DoHQuerySumary) GetQuery() []byte { return q.summ.Query } +func (q DoHQuerySumary) GetResponse() []byte { return q.summ.Response } +func (q DoHQuerySumary) GetServer() string { return q.summ.Server } +func (q DoHQuerySumary) GetStatus() DoHStatus { return q.summ.Status } +func (q DoHQuerySumary) GetHTTPStatus() int { return q.summ.HTTPStatus } +func (q DoHQuerySumary) GetLatency() float64 { return q.summ.Latency } // dohListenerAdapter is an adapter for the internal [doh.Listener]. type dohListenerAdapter struct { @@ -148,5 +148,5 @@ func (e dohListenerAdapter) OnQuery(url string) doh.Token { } func (e dohListenerAdapter) OnResponse(t doh.Token, s *doh.Summary) { - e.l.OnResponse(t, &DoHQueryStats{s}) + e.l.OnResponse(t, &DoHQuerySumary{s}) } diff --git a/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java b/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java index 352fb8a1..d1af2e89 100644 --- a/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java +++ b/Android/app/src/main/java/app/intra/net/go/GoIntraListener.java @@ -28,7 +28,7 @@ import app.intra.sys.firebase.AnalyticsWrapper; import backend.Backend; import backend.DoHListener; -import backend.DoHQueryStats; +import backend.DoHQuerySumary; import backend.DoHQueryToken; import intra.TCPSocketSummary; import intra.UDPSocketSummary; @@ -125,30 +125,30 @@ private static int len(byte[] a) { } @Override - public void onResponse(DoHQueryToken token, DoHQueryStats stats) { - if (stats.getHTTPStatus() != 0 && token != null) { + public void onResponse(DoHQueryToken token, DoHQuerySumary summary) { + if (summary.getHTTPStatus() != 0 && token != null) { // HTTP transaction completed. Report performance metrics. Metric m = (Metric)token; - m.metric.setRequestPayloadSize(len(stats.getQuery())); - m.metric.setHttpResponseCode((int)stats.getHTTPStatus()); - m.metric.setResponsePayloadSize(len(stats.getResponse())); + m.metric.setRequestPayloadSize(len(summary.getQuery())); + m.metric.setHttpResponseCode((int)summary.getHTTPStatus()); + m.metric.setResponsePayloadSize(len(summary.getResponse())); m.metric.stop(); // Finalizes the metric and queues it for upload. } final DnsPacket query; try { - query = new DnsPacket(stats.getQuery()); + query = new DnsPacket(summary.getQuery()); } catch (ProtocolException e) { return; } - long latencyMs = (long)(1000 * stats.getLatency()); + long latencyMs = (long)(1000 * summary.getLatency()); long nowMs = SystemClock.elapsedRealtime(); long queryTimeMs = nowMs - latencyMs; Transaction transaction = new Transaction(query, queryTimeMs); - transaction.response = stats.getResponse(); - transaction.responseTime = (long)(1000 * stats.getLatency()); - transaction.serverIp = stats.getServer(); - transaction.status = goStatusMap.get(stats.getStatus()); + transaction.response = summary.getResponse(); + transaction.responseTime = (long)(1000 * summary.getLatency()); + transaction.serverIp = summary.getServer(); + transaction.status = goStatusMap.get(summary.getStatus()); transaction.responseCalendar = Calendar.getInstance(); vpnService.recordTransaction(transaction); From 9ec48a401593631efda046cc4ab12e1e646abee0 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 21 Feb 2024 15:20:56 -0500 Subject: [PATCH 16/17] replace log with slog --- Android/app/src/go/backend/tunnel.go | 6 +- Android/app/src/go/doh/client_auth.go | 11 ++-- Android/app/src/go/doh/doh.go | 70 ++++++++++----------- Android/app/src/go/doh/ipmap/ipmap.go | 5 +- Android/app/src/go/intra/protect/protect.go | 2 +- Android/app/src/go/intra/sni_reporter.go | 10 +-- Android/app/src/go/intra/split/retrier.go | 13 ++-- Android/app/src/go/logging/logging.go | 61 ++++++++++++++++-- 8 files changed, 112 insertions(+), 66 deletions(-) diff --git a/Android/app/src/go/backend/tunnel.go b/Android/app/src/go/backend/tunnel.go index 5c9058af..12813cc5 100644 --- a/Android/app/src/go/backend/tunnel.go +++ b/Android/app/src/go/backend/tunnel.go @@ -20,8 +20,8 @@ import ( "io/fs" "localhost/Intra/Android/app/src/go/intra" "localhost/Intra/Android/app/src/go/intra/protect" + "localhost/Intra/Android/app/src/go/logging" "localhost/Intra/Android/app/src/go/tuntap" - "log" "os" "github.com/Jigsaw-Code/outline-sdk/network" @@ -74,8 +74,8 @@ func ConnectSession( } func copyUntilEOF(dst, src io.ReadWriteCloser) { - log.Printf("[debug] start relaying traffic [%s] -> [%s]", src, dst) - defer log.Printf("[debug] stop relaying traffic [%s] -> [%s]", src, dst) + logging.Dbg("IntraSession(copyUntilEOF) - start relaying traffic", "src", src, "dst", dst) + defer logging.Dbg("IntraSession(copyUntilEOF) - stop relaying traffic", "src", src, "dst", dst) const commonMTU = 1500 buf := make([]byte, commonMTU) diff --git a/Android/app/src/go/doh/client_auth.go b/Android/app/src/go/doh/client_auth.go index c22f1936..10c5cf49 100644 --- a/Android/app/src/go/doh/client_auth.go +++ b/Android/app/src/go/doh/client_auth.go @@ -21,7 +21,6 @@ import ( "crypto/x509" "errors" "io" - "localhost/Intra/Android/app/src/go/logging" ) @@ -52,12 +51,12 @@ type clientAuthWrapper struct { func (ca *clientAuthWrapper) GetClientCertificate( info *tls.CertificateRequestInfo) (*tls.Certificate, error) { if ca.signer == nil { - logging.Warn.Println("Client certificate requested but not supported") + logging.Warn("Client certificate requested but not supported") return &tls.Certificate{}, nil } cert := ca.signer.GetClientCertificate() if cert == nil { - logging.Warn.Println("Unable to fetch client certificate") + logging.Warn("Unable to fetch client certificate") return &tls.Certificate{}, nil } chain := [][]byte{cert} @@ -67,13 +66,13 @@ func (ca *clientAuthWrapper) GetClientCertificate( } leaf, err := x509.ParseCertificate(cert) if err != nil { - logging.Warn.Printf("Unable to parse client certificate: %v\n", err) + logging.Warnf("Unable to parse client certificate: %v", err) return &tls.Certificate{}, nil } _, isECDSA := leaf.PublicKey.(*ecdsa.PublicKey) if !isECDSA { // RSA-PSS and RSA-SSA both need explicit signature generation support. - logging.Warn.Println("Only ECDSA client certificates are supported") + logging.Warn("Only ECDSA client certificates are supported") return &tls.Certificate{}, nil } return &tls.Certificate{ @@ -91,7 +90,7 @@ func (ca *clientAuthWrapper) Public() crypto.PublicKey { cert := ca.signer.GetClientCertificate() leaf, err := x509.ParseCertificate(cert) if err != nil { - logging.Warn.Printf("Unable to parse client certificate: %v\n", err) + logging.Warnf("Unable to parse client certificate: %v", err) return nil } return leaf.PublicKey diff --git a/Android/app/src/go/doh/doh.go b/Android/app/src/go/doh/doh.go index cb7abf19..116e79f0 100644 --- a/Android/app/src/go/doh/doh.go +++ b/Android/app/src/go/doh/doh.go @@ -109,7 +109,7 @@ type resolver struct { const tcpTimeout time.Duration = 3 * time.Second func (r *resolver) dial(ctx context.Context, network, addr string) (net.Conn, error) { - logging.Debug.Printf("Dialing %s\n", addr) + logging.Dbg("DoH(resolver.dial) - dialing", "addr", addr) domain, portStr, err := net.SplitHostPort(addr) if err != nil { return nil, err @@ -128,23 +128,23 @@ func (r *resolver) dial(ctx context.Context, network, addr string) (net.Conn, er ips := r.ips.Get(domain) confirmed := ips.Confirmed() if confirmed != nil { - logging.Debug.Printf("Trying confirmed IP %s for addr %s\n", confirmed.String(), addr) + logging.Dbg("DoH(resolver.dial) - trying confirmed IP", "confirmedIP", confirmed, "addr", addr) if conn, err = split.DialWithSplitRetry(ctx, r.dialer, tcpaddr(confirmed), nil); err == nil { - logging.Info.Printf("Confirmed IP %s worked\n", confirmed.String()) + logging.Info("DoH(resolver.dial) - confirmed IP worked", "confirmedIP", confirmed) return conn, nil } - logging.Debug.Printf("Confirmed IP %s failed with err %v\n", confirmed.String(), err) + logging.Dbg("DoH(resolver.dial) - confirmed IP failed", "confirmedIP", confirmed, "err", err) ips.Disconfirm(confirmed) } - logging.Debug.Println("Trying all IPs") + logging.Dbg("DoH(resolver.dial) - trying all IPs") for _, ip := range ips.GetAll() { if ip.Equal(confirmed) { // Don't try this IP twice. continue } if conn, err = split.DialWithSplitRetry(ctx, r.dialer, tcpaddr(ip), nil); err == nil { - logging.Info.Printf("Found working IP: %s\n", ip.String()) + logging.Info("DoH(resolver.dial) - found working IP", "ip", ip) return conn, nil } } @@ -328,13 +328,13 @@ func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, h if qerr == nil { return } - logging.Info.Printf("%d Query failed: %v\n", id, qerr) + logging.Info("DoH(resolver.sendRequest) - done", "id", id, "queryError", qerr) if server != nil { - logging.Debug.Printf("%d Disconfirming %s\n", id, server.IP.String()) + logging.Dbg("DoH(resolver.sendRequest) - disconfirming IP", "id", id, "ip", server.IP) r.ips.Get(hostname).Disconfirm(server.IP) } if conn != nil { - logging.Info.Printf("%d Closing failing DoH socket\n", id) + logging.Info("DoH(resolver.sendRequest) - closing failing DoH socket", "id", id) conn.Close() } }() @@ -345,10 +345,10 @@ func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, h // reading the variables it has set. trace := httptrace.ClientTrace{ GetConn: func(hostPort string) { - logging.Debug.Printf("%d GetConn(%s)\n", id, hostPort) + logging.Dbgf("%d GetConn(%s)", id, hostPort) }, GotConn: func(info httptrace.GotConnInfo) { - logging.Debug.Printf("%d GotConn(%v)\n", id, info) + logging.Dbgf("%d GotConn(%v)", id, info) if info.Conn == nil { return } @@ -357,41 +357,41 @@ func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, h server = conn.RemoteAddr().(*net.TCPAddr) }, PutIdleConn: func(err error) { - logging.Debug.Printf("%d PutIdleConn(%v)\n", id, err) + logging.Dbgf("%d PutIdleConn(%v)", id, err) }, GotFirstResponseByte: func() { - logging.Debug.Printf("%d GotFirstResponseByte()\n", id) + logging.Dbgf("%d GotFirstResponseByte()", id) }, Got100Continue: func() { - logging.Debug.Printf("%d Got100Continue()\n", id) + logging.Dbgf("%d Got100Continue()", id) }, Got1xxResponse: func(code int, header textproto.MIMEHeader) error { - logging.Debug.Printf("%d Got1xxResponse(%d, %v)\n", id, code, header) + logging.Dbgf("%d Got1xxResponse(%d, %v)", id, code, header) return nil }, DNSStart: func(info httptrace.DNSStartInfo) { - logging.Debug.Printf("%d DNSStart(%v)\n", id, info) + logging.Dbgf("%d DNSStart(%v)", id, info) }, DNSDone: func(info httptrace.DNSDoneInfo) { - logging.Debug.Printf("%d, DNSDone(%v)\n", id, info) + logging.Dbgf("%d, DNSDone(%v)", id, info) }, ConnectStart: func(network, addr string) { - logging.Debug.Printf("%d ConnectStart(%s, %s)\n", id, network, addr) + logging.Dbgf("%d ConnectStart(%s, %s)", id, network, addr) }, ConnectDone: func(network, addr string, err error) { - logging.Debug.Printf("%d ConnectDone(%s, %s, %v)\n", id, network, addr, err) + logging.Dbgf("%d ConnectDone(%s, %s, %v)", id, network, addr, err) }, TLSHandshakeStart: func() { - logging.Debug.Printf("%d TLSHandshakeStart()\n", id) + logging.Dbgf("%d TLSHandshakeStart()", id) }, TLSHandshakeDone: func(state tls.ConnectionState, err error) { - logging.Debug.Printf("%d TLSHandshakeDone(%v, %v)\n", id, state, err) + logging.Dbgf("%d TLSHandshakeDone(%v, %v)", id, state, err) }, WroteHeaders: func() { - logging.Debug.Printf("%d WroteHeaders()\n", id) + logging.Dbgf("%d WroteHeaders()", id) }, WroteRequest: func(info httptrace.WroteRequestInfo) { - logging.Debug.Printf("%d WroteRequest(%v)\n", id, info) + logging.Dbgf("%d WroteRequest(%v)", id, info) }, } req = req.WithContext(httptrace.WithClientTrace(req.Context(), &trace)) @@ -400,20 +400,20 @@ func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, h req.Header.Set("Content-Type", mimetype) req.Header.Set("Accept", mimetype) req.Header.Set("User-Agent", "Intra") - logging.Debug.Printf("%d Sending query\n", id) + logging.Dbg("DoH(resolver.sendRequest) - sending query", "id", id) httpResponse, err := r.client.Do(req) if err != nil { qerr = &queryError{SendFailed, err} return } - logging.Debug.Printf("%d Got response\n", id) + logging.Dbg("DoH(resolver.sendRequest) - got response", "id", id) response, err = io.ReadAll(httpResponse.Body) if err != nil { qerr = &queryError{BadResponse, err} return } httpResponse.Body.Close() - logging.Debug.Printf("%d Closed response\n", id) + logging.Dbg("DoH(resolver.sendRequest) - response closed", "id", id) // Update the hostname, which could have changed due to a redirect. hostname = httpResponse.Request.URL.Hostname() @@ -423,7 +423,7 @@ func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, h req.Write(reqBuf) respBuf := new(bytes.Buffer) httpResponse.Write(respBuf) - logging.Debug.Printf("%d request: %s\nresponse: %s\n", id, reqBuf.String(), respBuf.String()) + logging.Dbg("DoH(resolver.sendRequest) - response invalid", "id", id, "req", reqBuf, "resp", respBuf) qerr = &queryError{HTTPError, &httpError{httpResponse.StatusCode}} return @@ -521,7 +521,7 @@ func forwardQuery(r Resolver, q []byte, c io.Writer) error { // and close the writer if there was an error. func forwardQueryAndCheck(r Resolver, q []byte, c io.WriteCloser) { if err := forwardQuery(r, q, c); err != nil { - logging.Warn.Printf("Query forwarding failed: %v\n", err) + logging.Warn("DoH(forwardQueryAndCheck) - forwarding failed", "err", err) c.Close() } } @@ -532,26 +532,26 @@ func Accept(r Resolver, c io.ReadWriteCloser) { for { n, err := c.Read(qlbuf) if n == 0 { - logging.Debug.Println("TCP query socket clean shutdown") + logging.Dbg("DoH(Accept) - TCP query socket clean shutdown") break } if err != nil { - logging.Warn.Printf("Error reading from TCP query socket: %v\n", err) + logging.Warn("DoH(Accept) - failed to read from TCP query socket", "err", err) break } if n < 2 { - logging.Warn.Println("Incomplete query length") + logging.Warn("DoH(Accept) - incomplete query length") break } qlen := binary.BigEndian.Uint16(qlbuf) q := make([]byte, qlen) n, err = c.Read(q) if err != nil { - logging.Warn.Printf("Error reading query: %v\n", err) + logging.Warn("DoH(Accept) - failed to read query", "err", err) break } if n != int(qlen) { - logging.Warn.Printf("Incomplete query: %d < %d\n", n, qlen) + logging.Warn("DoH(Accept) - incomplete query (n < qlen)", "n", n, "qlen", qlen) break } go forwardQueryAndCheck(r, q, c) @@ -562,7 +562,7 @@ func Accept(r Resolver, c io.ReadWriteCloser) { // Servfail returns a SERVFAIL response to the query q. func Servfail(q []byte) ([]byte, error) { - defer logging.Debug.Println("SERVFAIL response generated") + defer logging.Dbg("DoH(SERVFAIL) - response generated") var msg dnsmessage.Message if err := msg.Unpack(q); err != nil { return nil, err @@ -577,7 +577,7 @@ func Servfail(q []byte) ([]byte, error) { func tryServfail(q []byte) []byte { response, err := Servfail(q) if err != nil { - logging.Warn.Printf("Error constructing servfail: %v\n", err) + logging.Warn("DoH(SERVFAIL) - failed to construct response", "err", err) } return response } diff --git a/Android/app/src/go/doh/ipmap/ipmap.go b/Android/app/src/go/doh/ipmap/ipmap.go index 479ba72c..63444ece 100644 --- a/Android/app/src/go/doh/ipmap/ipmap.go +++ b/Android/app/src/go/doh/ipmap/ipmap.go @@ -16,11 +16,10 @@ package ipmap import ( "context" + "localhost/Intra/Android/app/src/go/logging" "math/rand" "net" "sync" - - "localhost/Intra/Android/app/src/go/logging" ) // IPMap maps hostnames to IPSets. @@ -104,7 +103,7 @@ func (s *IPSet) Add(hostname string) { // Don't hold the ipMap lock during blocking I/O. resolved, err := s.r.LookupIPAddr(context.TODO(), hostname) if err != nil { - logging.Warn.Printf("Failed to resolve %s: %v\n", hostname, err) + logging.Warnf("Failed to resolve %s: %v", hostname, err) } s.Lock() for _, addr := range resolved { diff --git a/Android/app/src/go/intra/protect/protect.go b/Android/app/src/go/intra/protect/protect.go index ee669d8c..d3de1be0 100644 --- a/Android/app/src/go/intra/protect/protect.go +++ b/Android/app/src/go/intra/protect/protect.go @@ -44,7 +44,7 @@ func makeControl(p Protector) func(string, string, syscall.RawConn) error { return c.Control(func(fd uintptr) { if !p.Protect(int32(fd)) { // TODO: Record and report these errors. - logging.Err.Printf("Failed to protect a %s socket\n", network) + logging.Errf("Failed to protect a %s socket", network) } }) } diff --git a/Android/app/src/go/intra/sni_reporter.go b/Android/app/src/go/intra/sni_reporter.go index 07353fcf..4687df7d 100644 --- a/Android/app/src/go/intra/sni_reporter.go +++ b/Android/app/src/go/intra/sni_reporter.go @@ -62,11 +62,11 @@ func (r *tcpSNIReporter) Send(report choir.Report) error { r.mu.RUnlock() q, err := choir.FormatQuery(report, suffix) if err != nil { - logging.Warn.Printf("Failed to construct query for Choir: %v\n", err) + logging.Warnf("Failed to construct query for Choir: %v", err) return nil } if _, err = dns.Query(context.Background(), q); err != nil { - logging.Info.Printf("Failed to deliver query for Choir: %v\n", err) + logging.Infof("Failed to deliver query for Choir: %v", err) } return nil } @@ -106,13 +106,13 @@ func (r *tcpSNIReporter) Report(summary TCPSocketSummary) { } resultValue, err := choir.NewValue(result) if err != nil { - logging.Err.Printf("Bad result %s: %v\n", result, err) + logging.Errf("Bad result %s: %v", result, err) } responseValue, err := choir.NewValue(response) if err != nil { - logging.Err.Printf("Bad response %s: %v\n", response, err) + logging.Errf("Bad response %s: %v", response, err) } if err := reporter.Report(summary.Retry.SNI, resultValue, responseValue); err != nil { - logging.Warn.Printf("Choir report failed: %v\n", err) + logging.Warnf("Choir report failed: %v", err) } } diff --git a/Android/app/src/go/intra/split/retrier.go b/Android/app/src/go/intra/split/retrier.go index 7334e7ae..f824bdf6 100644 --- a/Android/app/src/go/intra/split/retrier.go +++ b/Android/app/src/go/intra/split/retrier.go @@ -18,13 +18,12 @@ import ( "context" "errors" "io" + "localhost/Intra/Android/app/src/go/logging" "math/rand" "net" "sync" "time" - "localhost/Intra/Android/app/src/go/logging" - "github.com/Jigsaw-Code/getsni" ) @@ -121,10 +120,10 @@ const DefaultTimeout time.Duration = 0 // `addr` is the destination. // If `stats` is non-nil, it will be populated with retry-related information. func DialWithSplitRetry(ctx context.Context, dialer *net.Dialer, addr *net.TCPAddr, stats *RetryStats) (DuplexConn, error) { - logging.Debug.Printf("Split-Retry: dialing to %v...\n", addr) + logging.Dbg("SplitRetry(DialWithSplitRetry) - dialing", "addr", addr) before := time.Now() conn, err := dialer.DialContext(ctx, addr.Network(), addr.String()) - logging.Debug.Printf("Split-Retry: conn dialed, err = %v\n", err) + logging.Dbg("SplitRetry(DialWithSplitRetry) - dialed", "err", err) if err != nil { return nil, err } @@ -167,7 +166,7 @@ func (r *retrier) Read(buf []byte) (n int, err error) { // Read failed. Retry. n, err = r.retry(buf) } - logging.Debug.Println("Split-Retry: direct conn succeeded, no need to split") + logging.Dbg("SplitRetry(retrier.Read) - direct conn succeeded, no need to split") close(r.retryCompleteFlag) // Unset read deadline. r.conn.SetReadDeadline(time.Time{}) @@ -178,8 +177,8 @@ func (r *retrier) Read(buf []byte) (n int, err error) { } func (r *retrier) retry(buf []byte) (n int, err error) { - logging.Debug.Println("Split-Retry: retrying...") - defer func() { logging.Debug.Printf("Split-Retry: retried n = %v, err = %v\n", n, err) }() + logging.Dbg("SplitRetry(retrier.retry) - retrying...") + defer func() { logging.Dbg("SplitRetry(retrier.retry) - retried", "n", n, "err", err) }() r.conn.Close() var newConn net.Conn diff --git a/Android/app/src/go/logging/logging.go b/Android/app/src/go/logging/logging.go index 062dd3a9..7403d4bf 100644 --- a/Android/app/src/go/logging/logging.go +++ b/Android/app/src/go/logging/logging.go @@ -12,15 +12,64 @@ // See the License for the specific language governing permissions and // limitations under the License. +/* +Package logging is a centralized logging system for Intra's Go backend. +It offers efficient logging methods that save CPU power by only formatting +messages that need to be logged. +*/ package logging import ( - "io" - "log" + "context" + "fmt" + "log/slog" "os" ) -var Debug = log.New(io.Discard, "[DEBUG] ", log.LstdFlags) -var Info = log.New(io.Discard, "[INFO] ", log.LstdFlags) -var Warn = log.New(os.Stderr, "[WARN] ", log.LstdFlags) -var Err = log.New(os.Stderr, "[ERROR] ", log.LstdFlags) +var logger = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + Level: slog.LevelWarn, +})) + +func Dbg(msg string, args ...any) { + logger.Debug(msg, args...) +} + +func Dbgf(format string, args ...any) { + if !logger.Enabled(context.Background(), slog.LevelDebug) { + return + } + logger.Debug(fmt.Sprintf(format, args...)) +} + +func Info(msg string, args ...any) { + logger.Info(msg, args...) +} + +func Infof(format string, args ...any) { + if !logger.Enabled(context.Background(), slog.LevelInfo) { + return + } + logger.Info(fmt.Sprintf(format, args...)) +} + +func Warn(msg string, args ...any) { + logger.Warn(msg, args...) +} + +func Warnf(format string, args ...any) { + if !logger.Enabled(context.Background(), slog.LevelWarn) { + return + } + logger.Warn(fmt.Sprintf(format, args...)) +} + +func Err(msg string, args ...any) { + logger.Error(msg, args...) +} + +func Errf(format string, args ...any) { + if !logger.Enabled(context.Background(), slog.LevelError) { + return + } + logger.Error(fmt.Sprintf(format, args...)) +} From 7e4bed2de6e4046f666791b2f7d28b35f996bd13 Mon Sep 17 00:00:00 2001 From: jyyi1 Date: Wed, 21 Feb 2024 18:50:58 -0500 Subject: [PATCH 17/17] rename logging functions to align with slog --- Android/app/src/go/backend/tunnel.go | 4 +- Android/app/src/go/doh/doh.go | 50 ++++++++++----------- Android/app/src/go/intra/protect/protect.go | 2 +- Android/app/src/go/intra/sni_reporter.go | 4 +- Android/app/src/go/intra/split/retrier.go | 10 ++--- Android/app/src/go/logging/logging.go | 8 ++-- 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/Android/app/src/go/backend/tunnel.go b/Android/app/src/go/backend/tunnel.go index 12813cc5..63d3dd18 100644 --- a/Android/app/src/go/backend/tunnel.go +++ b/Android/app/src/go/backend/tunnel.go @@ -74,8 +74,8 @@ func ConnectSession( } func copyUntilEOF(dst, src io.ReadWriteCloser) { - logging.Dbg("IntraSession(copyUntilEOF) - start relaying traffic", "src", src, "dst", dst) - defer logging.Dbg("IntraSession(copyUntilEOF) - stop relaying traffic", "src", src, "dst", dst) + logging.Debug("IntraSession(copyUntilEOF) - start relaying traffic", "src", src, "dst", dst) + defer logging.Debug("IntraSession(copyUntilEOF) - stop relaying traffic", "src", src, "dst", dst) const commonMTU = 1500 buf := make([]byte, commonMTU) diff --git a/Android/app/src/go/doh/doh.go b/Android/app/src/go/doh/doh.go index 116e79f0..82e2f69a 100644 --- a/Android/app/src/go/doh/doh.go +++ b/Android/app/src/go/doh/doh.go @@ -109,7 +109,7 @@ type resolver struct { const tcpTimeout time.Duration = 3 * time.Second func (r *resolver) dial(ctx context.Context, network, addr string) (net.Conn, error) { - logging.Dbg("DoH(resolver.dial) - dialing", "addr", addr) + logging.Debug("DoH(resolver.dial) - dialing", "addr", addr) domain, portStr, err := net.SplitHostPort(addr) if err != nil { return nil, err @@ -128,16 +128,16 @@ func (r *resolver) dial(ctx context.Context, network, addr string) (net.Conn, er ips := r.ips.Get(domain) confirmed := ips.Confirmed() if confirmed != nil { - logging.Dbg("DoH(resolver.dial) - trying confirmed IP", "confirmedIP", confirmed, "addr", addr) + logging.Debug("DoH(resolver.dial) - trying confirmed IP", "confirmedIP", confirmed, "addr", addr) if conn, err = split.DialWithSplitRetry(ctx, r.dialer, tcpaddr(confirmed), nil); err == nil { logging.Info("DoH(resolver.dial) - confirmed IP worked", "confirmedIP", confirmed) return conn, nil } - logging.Dbg("DoH(resolver.dial) - confirmed IP failed", "confirmedIP", confirmed, "err", err) + logging.Debug("DoH(resolver.dial) - confirmed IP failed", "confirmedIP", confirmed, "err", err) ips.Disconfirm(confirmed) } - logging.Dbg("DoH(resolver.dial) - trying all IPs") + logging.Debug("DoH(resolver.dial) - trying all IPs") for _, ip := range ips.GetAll() { if ip.Equal(confirmed) { // Don't try this IP twice. @@ -330,7 +330,7 @@ func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, h } logging.Info("DoH(resolver.sendRequest) - done", "id", id, "queryError", qerr) if server != nil { - logging.Dbg("DoH(resolver.sendRequest) - disconfirming IP", "id", id, "ip", server.IP) + logging.Debug("DoH(resolver.sendRequest) - disconfirming IP", "id", id, "ip", server.IP) r.ips.Get(hostname).Disconfirm(server.IP) } if conn != nil { @@ -345,10 +345,10 @@ func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, h // reading the variables it has set. trace := httptrace.ClientTrace{ GetConn: func(hostPort string) { - logging.Dbgf("%d GetConn(%s)", id, hostPort) + logging.Debugf("%d GetConn(%s)", id, hostPort) }, GotConn: func(info httptrace.GotConnInfo) { - logging.Dbgf("%d GotConn(%v)", id, info) + logging.Debugf("%d GotConn(%v)", id, info) if info.Conn == nil { return } @@ -357,41 +357,41 @@ func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, h server = conn.RemoteAddr().(*net.TCPAddr) }, PutIdleConn: func(err error) { - logging.Dbgf("%d PutIdleConn(%v)", id, err) + logging.Debugf("%d PutIdleConn(%v)", id, err) }, GotFirstResponseByte: func() { - logging.Dbgf("%d GotFirstResponseByte()", id) + logging.Debugf("%d GotFirstResponseByte()", id) }, Got100Continue: func() { - logging.Dbgf("%d Got100Continue()", id) + logging.Debugf("%d Got100Continue()", id) }, Got1xxResponse: func(code int, header textproto.MIMEHeader) error { - logging.Dbgf("%d Got1xxResponse(%d, %v)", id, code, header) + logging.Debugf("%d Got1xxResponse(%d, %v)", id, code, header) return nil }, DNSStart: func(info httptrace.DNSStartInfo) { - logging.Dbgf("%d DNSStart(%v)", id, info) + logging.Debugf("%d DNSStart(%v)", id, info) }, DNSDone: func(info httptrace.DNSDoneInfo) { - logging.Dbgf("%d, DNSDone(%v)", id, info) + logging.Debugf("%d, DNSDone(%v)", id, info) }, ConnectStart: func(network, addr string) { - logging.Dbgf("%d ConnectStart(%s, %s)", id, network, addr) + logging.Debugf("%d ConnectStart(%s, %s)", id, network, addr) }, ConnectDone: func(network, addr string, err error) { - logging.Dbgf("%d ConnectDone(%s, %s, %v)", id, network, addr, err) + logging.Debugf("%d ConnectDone(%s, %s, %v)", id, network, addr, err) }, TLSHandshakeStart: func() { - logging.Dbgf("%d TLSHandshakeStart()", id) + logging.Debugf("%d TLSHandshakeStart()", id) }, TLSHandshakeDone: func(state tls.ConnectionState, err error) { - logging.Dbgf("%d TLSHandshakeDone(%v, %v)", id, state, err) + logging.Debugf("%d TLSHandshakeDone(%v, %v)", id, state, err) }, WroteHeaders: func() { - logging.Dbgf("%d WroteHeaders()", id) + logging.Debugf("%d WroteHeaders()", id) }, WroteRequest: func(info httptrace.WroteRequestInfo) { - logging.Dbgf("%d WroteRequest(%v)", id, info) + logging.Debugf("%d WroteRequest(%v)", id, info) }, } req = req.WithContext(httptrace.WithClientTrace(req.Context(), &trace)) @@ -400,20 +400,20 @@ func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, h req.Header.Set("Content-Type", mimetype) req.Header.Set("Accept", mimetype) req.Header.Set("User-Agent", "Intra") - logging.Dbg("DoH(resolver.sendRequest) - sending query", "id", id) + logging.Debug("DoH(resolver.sendRequest) - sending query", "id", id) httpResponse, err := r.client.Do(req) if err != nil { qerr = &queryError{SendFailed, err} return } - logging.Dbg("DoH(resolver.sendRequest) - got response", "id", id) + logging.Debug("DoH(resolver.sendRequest) - got response", "id", id) response, err = io.ReadAll(httpResponse.Body) if err != nil { qerr = &queryError{BadResponse, err} return } httpResponse.Body.Close() - logging.Dbg("DoH(resolver.sendRequest) - response closed", "id", id) + logging.Debug("DoH(resolver.sendRequest) - response closed", "id", id) // Update the hostname, which could have changed due to a redirect. hostname = httpResponse.Request.URL.Hostname() @@ -423,7 +423,7 @@ func (r *resolver) sendRequest(id uint16, req *http.Request) (response []byte, h req.Write(reqBuf) respBuf := new(bytes.Buffer) httpResponse.Write(respBuf) - logging.Dbg("DoH(resolver.sendRequest) - response invalid", "id", id, "req", reqBuf, "resp", respBuf) + logging.Debug("DoH(resolver.sendRequest) - response invalid", "id", id, "req", reqBuf, "resp", respBuf) qerr = &queryError{HTTPError, &httpError{httpResponse.StatusCode}} return @@ -532,7 +532,7 @@ func Accept(r Resolver, c io.ReadWriteCloser) { for { n, err := c.Read(qlbuf) if n == 0 { - logging.Dbg("DoH(Accept) - TCP query socket clean shutdown") + logging.Debug("DoH(Accept) - TCP query socket clean shutdown") break } if err != nil { @@ -562,7 +562,7 @@ func Accept(r Resolver, c io.ReadWriteCloser) { // Servfail returns a SERVFAIL response to the query q. func Servfail(q []byte) ([]byte, error) { - defer logging.Dbg("DoH(SERVFAIL) - response generated") + defer logging.Debug("DoH(SERVFAIL) - response generated") var msg dnsmessage.Message if err := msg.Unpack(q); err != nil { return nil, err diff --git a/Android/app/src/go/intra/protect/protect.go b/Android/app/src/go/intra/protect/protect.go index d3de1be0..87e5fcb7 100644 --- a/Android/app/src/go/intra/protect/protect.go +++ b/Android/app/src/go/intra/protect/protect.go @@ -44,7 +44,7 @@ func makeControl(p Protector) func(string, string, syscall.RawConn) error { return c.Control(func(fd uintptr) { if !p.Protect(int32(fd)) { // TODO: Record and report these errors. - logging.Errf("Failed to protect a %s socket", network) + logging.Errorf("Failed to protect a %s socket", network) } }) } diff --git a/Android/app/src/go/intra/sni_reporter.go b/Android/app/src/go/intra/sni_reporter.go index 4687df7d..f1a3dd7f 100644 --- a/Android/app/src/go/intra/sni_reporter.go +++ b/Android/app/src/go/intra/sni_reporter.go @@ -106,11 +106,11 @@ func (r *tcpSNIReporter) Report(summary TCPSocketSummary) { } resultValue, err := choir.NewValue(result) if err != nil { - logging.Errf("Bad result %s: %v", result, err) + logging.Errorf("Bad result %s: %v", result, err) } responseValue, err := choir.NewValue(response) if err != nil { - logging.Errf("Bad response %s: %v", response, err) + logging.Errorf("Bad response %s: %v", response, err) } if err := reporter.Report(summary.Retry.SNI, resultValue, responseValue); err != nil { logging.Warnf("Choir report failed: %v", err) diff --git a/Android/app/src/go/intra/split/retrier.go b/Android/app/src/go/intra/split/retrier.go index f824bdf6..7367ec9e 100644 --- a/Android/app/src/go/intra/split/retrier.go +++ b/Android/app/src/go/intra/split/retrier.go @@ -120,10 +120,10 @@ const DefaultTimeout time.Duration = 0 // `addr` is the destination. // If `stats` is non-nil, it will be populated with retry-related information. func DialWithSplitRetry(ctx context.Context, dialer *net.Dialer, addr *net.TCPAddr, stats *RetryStats) (DuplexConn, error) { - logging.Dbg("SplitRetry(DialWithSplitRetry) - dialing", "addr", addr) + logging.Debug("SplitRetry(DialWithSplitRetry) - dialing", "addr", addr) before := time.Now() conn, err := dialer.DialContext(ctx, addr.Network(), addr.String()) - logging.Dbg("SplitRetry(DialWithSplitRetry) - dialed", "err", err) + logging.Debug("SplitRetry(DialWithSplitRetry) - dialed", "err", err) if err != nil { return nil, err } @@ -166,7 +166,7 @@ func (r *retrier) Read(buf []byte) (n int, err error) { // Read failed. Retry. n, err = r.retry(buf) } - logging.Dbg("SplitRetry(retrier.Read) - direct conn succeeded, no need to split") + logging.Debug("SplitRetry(retrier.Read) - direct conn succeeded, no need to split") close(r.retryCompleteFlag) // Unset read deadline. r.conn.SetReadDeadline(time.Time{}) @@ -177,8 +177,8 @@ func (r *retrier) Read(buf []byte) (n int, err error) { } func (r *retrier) retry(buf []byte) (n int, err error) { - logging.Dbg("SplitRetry(retrier.retry) - retrying...") - defer func() { logging.Dbg("SplitRetry(retrier.retry) - retried", "n", n, "err", err) }() + logging.Debug("SplitRetry(retrier.retry) - retrying...") + defer func() { logging.Debug("SplitRetry(retrier.retry) - retried", "n", n, "err", err) }() r.conn.Close() var newConn net.Conn diff --git a/Android/app/src/go/logging/logging.go b/Android/app/src/go/logging/logging.go index 7403d4bf..510e6010 100644 --- a/Android/app/src/go/logging/logging.go +++ b/Android/app/src/go/logging/logging.go @@ -30,11 +30,11 @@ var logger = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelWarn, })) -func Dbg(msg string, args ...any) { +func Debug(msg string, args ...any) { logger.Debug(msg, args...) } -func Dbgf(format string, args ...any) { +func Debugf(format string, args ...any) { if !logger.Enabled(context.Background(), slog.LevelDebug) { return } @@ -63,11 +63,11 @@ func Warnf(format string, args ...any) { logger.Warn(fmt.Sprintf(format, args...)) } -func Err(msg string, args ...any) { +func Error(msg string, args ...any) { logger.Error(msg, args...) } -func Errf(format string, args ...any) { +func Errorf(format string, args ...any) { if !logger.Enabled(context.Background(), slog.LevelError) { return }