Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create Smart Dialer #185

Merged
merged 26 commits into from
Feb 22, 2024
Merged

feat: create Smart Dialer #185

merged 26 commits into from
Feb 22, 2024

Conversation

fortuna
Copy link
Contributor

@fortuna fortuna commented Feb 20, 2024

Preliminary code for the Smart Dialer and accompanying strategy finding.
I believe there's a lot to improve, but I'd rather move this to main sooner rather than later and iterate.

Sample output of the smart-proxy example app, with traffic going over a proxy in Russia:

go run ./examples/smart-proxy -v -localAddr=localhost:1080 -domain www.example.com -domain www.rferl.org -domain meduza.io -transport="$KEY" -config=./examples/smart-proxy/config.json
Finding strategy
πŸƒ run DNS: {"https":{"name":"2620:fe::fe"}} (domain: www.example.com.)
🏁 got DNS: {"https":{"name":"2620:fe::fe"}} (domain: www.example.com.), duration=429.375¡s, ips=[], status=request for A query failed: receive DNS message failed: failed to get HTTP response: Post "https://[2620:fe::fe]:443/dns-query": dial DNS resolver failed: IPv6 not supported ❌
πŸƒ run DNS: {"https":{"name":"9.9.9.9"}} (domain: www.example.com.)
πŸƒ run DNS: {"https":{"name":"149.112.112.112"}} (domain: www.example.com.)
πŸƒ run DNS: {"https":{"name":"2001:4860:4860::8888"}} (domain: www.example.com.)
🏁 got DNS: {"https":{"name":"2001:4860:4860::8888"}} (domain: www.example.com.), duration=84.25¡s, ips=[], status=request for A query failed: receive DNS message failed: failed to get HTTP response: Post "https://[2001:4860:4860::8888]:443/dns-query": dial DNS resolver failed: IPv6 not supported ❌
πŸƒ run DNS: {"https":{"name":"8.8.8.8"}} (domain: www.example.com.)
🏁 got DNS: {"https":{"name":"9.9.9.9"}} (domain: www.example.com.), duration=615.089042ms, ips=[93.184.216.34], status=ok βœ…
πŸƒ run DNS: {"https":{"name":"9.9.9.9"}} (domain: www.rferl.org.)
πŸƒ run DNS: {"https":{"name":"2001:4860:4860::8844"}} (domain: www.example.com.)
🏁 got DNS: {"https":{"name":"2001:4860:4860::8844"}} (domain: www.example.com.), duration=60.667¡s, ips=[], status=request for A query failed: receive DNS message failed: failed to get HTTP response: Post "https://[2001:4860:4860::8844]:443/dns-query": dial DNS resolver failed: IPv6 not supported ❌
πŸƒ run DNS: {"https":{"name":"8.8.4.4"}} (domain: www.example.com.)
🏁 got DNS: {"https":{"name":"9.9.9.9"}} (domain: www.rferl.org.), duration=204.264916ms, ips=[184.30.135.192], status=ok βœ…
πŸƒ run DNS: {"https":{"name":"9.9.9.9"}} (domain: meduza.io.)
🏁 got DNS: {"https":{"name":"149.112.112.112"}} (domain: www.example.com.), duration=621.707666ms, ips=[93.184.216.34], status=ok βœ…
πŸƒ run DNS: {"https":{"name":"149.112.112.112"}} (domain: www.rferl.org.)
πŸƒ run DNS: {"https":{"name":"2606:4700:4700::1111"}} (domain: www.example.com.)
🏁 got DNS: {"https":{"name":"2606:4700:4700::1111"}} (domain: www.example.com.), duration=483.583¡s, ips=[], status=request for A query failed: receive DNS message failed: failed to get HTTP response: Post "https://[2606:4700:4700::1111]:443/dns-query": dial DNS resolver failed: IPv6 not supported ❌
πŸƒ run DNS: {"https":{"name":"1.1.1.1"}} (domain: www.example.com.)
🏁 got DNS: {"https":{"name":"9.9.9.9"}} (domain: meduza.io.), duration=203.468ms, ips=[104.18.1.79 104.18.0.79], status=ok βœ…
πŸ† selected DNS resolver {"https":{"name":"9.9.9.9"}} in 1.02s

πŸƒ run TLS: '' (domain: www.example.com.)
πŸƒ run TLS: 'split:1' (domain: www.example.com.)
πŸƒ run TLS: 'split:2' (domain: www.example.com.)
πŸƒ run TLS: 'split:5' (domain: www.example.com.)
πŸƒ run TLS: 'tlsfrag:1' (domain: www.example.com.)
🏁 got TLS: '' (domain: www.example.com.), duration=1.086249791s, status=ok βœ…
πŸƒ run TLS: '' (domain: www.rferl.org.)
🏁 got TLS: 'split:1' (domain: www.example.com.), duration=877.371333ms, status=ok βœ…
πŸƒ run TLS: 'split:1' (domain: www.rferl.org.)
🏁 got TLS: 'split:2' (domain: www.example.com.), duration=881.26675ms, status=ok βœ…
πŸƒ run TLS: 'split:2' (domain: www.rferl.org.)
🏁 got TLS: 'split:5' (domain: www.example.com.), duration=877.222333ms, status=ok βœ…
πŸƒ run TLS: 'split:5' (domain: www.rferl.org.)
🏁 got TLS: '' (domain: www.rferl.org.), duration=640.737167ms, handshake=EOF ❌
🏁 got TLS: 'split:1' (domain: www.rferl.org.), duration=599.031ms, handshake=EOF ❌
🏁 got TLS: 'split:2' (domain: www.rferl.org.), duration=351.706167ms, handshake=EOF ❌
🏁 got TLS: 'tlsfrag:1' (domain: www.example.com.), duration=883.058291ms, status=ok βœ…
πŸƒ run TLS: 'tlsfrag:1' (domain: www.rferl.org.)
🏁 got TLS: 'split:5' (domain: www.rferl.org.), duration=352.326708ms, handshake=EOF ❌
🏁 got TLS: 'tlsfrag:1' (domain: www.rferl.org.), duration=470.467875ms, status=ok βœ…
πŸƒ run TLS: 'tlsfrag:1' (domain: meduza.io.)
🏁 got TLS: 'tlsfrag:1' (domain: meduza.io.), duration=589.686417ms, status=ok βœ…
πŸ† selected TLS strategy 'tlsfrag:1' in 2.95s

Found strategy in 3.97s
Proxy listening on 127.0.0.1:1080

This is an example of running it in Iran:

$ go run github.com/Jigsaw-Code/outline-sdk/x/examples/smart-proxy@fortuna-newsmart -v -localAddr=localhost:1080 -domain www.example.com -domain www.rferl.org -domain www.youtube.com --config=<(curl -s 'https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/fortuna-newsmart/x/examples/smart-proxy/config.json')
Finding strategy
πŸƒ run DNS: {"https":{"name":"2620:fe::fe"}} (domain: www.example.com.)
πŸƒ run DNS: {"https":{"name":"9.9.9.9"}} (domain: www.example.com.)
🏁 got DNS: {"https":{"name":"2620:fe::fe"}} (domain: www.example.com.), duration=273.182081ms, ips=[93.184.216.34], status=ok βœ…
πŸƒ run DNS: {"https":{"name":"2620:fe::fe"}} (domain: www.rferl.org.)
🏁 got DNS: {"https":{"name":"2620:fe::fe"}} (domain: www.rferl.org.), duration=101.217779ms, ips=[23.45.96.97], status=ok βœ…
πŸƒ run DNS: {"https":{"name":"2620:fe::fe"}} (domain: www.youtube.com.)
🏁 got DNS: {"https":{"name":"9.9.9.9"}} (domain: www.example.com.), duration=142.623087ms, ips=[93.184.216.34], status=ok βœ…
πŸƒ run DNS: {"https":{"name":"9.9.9.9"}} (domain: www.rferl.org.)
🏁 got DNS: {"https":{"name":"9.9.9.9"}} (domain: www.rferl.org.), duration=45.051125ms, ips=[2.17.138.137], status=ok βœ…
πŸƒ run DNS: {"https":{"name":"9.9.9.9"}} (domain: www.youtube.com.)
🏁 got DNS: {"https":{"name":"2620:fe::fe"}} (domain: www.youtube.com.), duration=72.290853ms, ips=[216.58.212.174 172.217.16.206 142.250.186.174 172.217.18.14 142.250.74.206 142.250.186.110 142.250.186.46 142.250.184.206 142.250.186.142 142.250.181.238 142.250.186.78 142.250.185.238 142.250.184.238 142.250.185.206 216.58.206.46 142.250.185.174], status=ok βœ…
πŸ† selected DNS resolver {"https":{"name":"2620:fe::fe"}} in 0.45s

πŸƒ run TLS: '' (domain: www.example.com.)
πŸƒ run TLS: 'split:1' (domain: www.example.com.)
πŸƒ run TLS: 'split:2' (domain: www.example.com.)
🏁 got TLS: '' (domain: www.example.com.), duration=605.941755ms, status=ok βœ…
πŸƒ run TLS: '' (domain: www.rferl.org.)
πŸƒ run TLS: 'tlsfrag:1' (domain: www.example.com.)
🏁 got TLS: '' (domain: www.rferl.org.), duration=187.651357ms, handshake=read tcp [IP]:48728->[2a02:26f0:3500:297::1317]:443: read: connection reset by peer ❌
🏁 got TLS: 'split:1' (domain: www.example.com.), duration=543.013237ms, status=ok βœ…
πŸƒ run TLS: 'split:1' (domain: www.rferl.org.)
🏁 got TLS: 'split:1' (domain: www.rferl.org.), duration=81.687075ms, handshake=read tcp [IP]:48732->[2a02:26f0:3500:297::1317]:443: read: connection reset by peer ❌
🏁 got TLS: 'split:2' (domain: www.example.com.), duration=484.49116ms, status=ok βœ…
πŸƒ run TLS: 'split:2' (domain: www.rferl.org.)
🏁 got TLS: 'split:2' (domain: www.rferl.org.), duration=80.590617ms, handshake=read tcp [IP]:48734->[2a02:26f0:3500:297::1317]:443: read: connection reset by peer ❌
🏁 got TLS: 'tlsfrag:1' (domain: www.example.com.), duration=482.842353ms, status=ok βœ…
πŸƒ run TLS: 'tlsfrag:1' (domain: www.rferl.org.)
🏁 got TLS: 'tlsfrag:1' (domain: www.rferl.org.), duration=228.793336ms, status=ok βœ…
πŸƒ run TLS: 'tlsfrag:1' (domain: www.youtube.com.)
🏁 got TLS: 'tlsfrag:1' (domain: www.youtube.com.), duration=238.114992ms, status=ok βœ…
πŸ† selected TLS strategy 'tlsfrag:1' in 1.70s

Found strategy in 2.15s
Proxy listening on 127.0.0.1:1080

@fortuna fortuna requested a review from jyyi1 February 20, 2024 22:02
@fortuna fortuna changed the title Create Smart Dialer feat: create Smart Dialer Feb 20, 2024
Copy link
Contributor

@jyyi1 jyyi1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Reviewed half of the PR, will review the code in x/smart later.

x/go.mod Show resolved Hide resolved
x/mobileproxy/mobileproxy.go Outdated Show resolved Hide resolved
x/mobileproxy/mobileproxy.go Outdated Show resolved Hide resolved
x/smart/cname_unix.go Show resolved Hide resolved
x/examples/smart-proxy/main.go Outdated Show resolved Hide resolved
x/examples/smart-proxy/main.go Show resolved Hide resolved
x/examples/smart-proxy/main.go Outdated Show resolved Hide resolved
@fortuna fortuna requested a review from jyyi1 February 21, 2024 18:24
Copy link
Contributor

@jyyi1 jyyi1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! The code LGTM, with a few small questions and suggestions.

x/mobileproxy/README.md Outdated Show resolved Hide resolved
x/mobileproxy/mobileproxy.go Outdated Show resolved Hide resolved
StreamDialer: &transport.TCPDialer{},
PacketDialer: &transport.UDPDialer{},
}
testDomainsSlice := append(make([]string, 0, len(testDomains.list)), testDomains.list...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question Why not using testDomain.list here? To make a deep copy of the testDomains because StringList is still mutable to the caller?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I've iterated on this code a lot, and I think at one point I was holding on to the list for later. There's no point in doing that now, since we don't refer to the list after NewDialer returns. I'm using your suggestion now

x/smart/racer.go Outdated Show resolved Hide resolved

go func(entry E, testDone context.CancelFunc) {
defer testDone()
result, err := test(entry)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not in this PR, test can accept a ctx object to it knows when to cancel the request. Because there might be resource leak here:

  1. go func () { test(entries[0]); }
  2. go func() { test(entries[1]); }
  3. test(entries[0]) finished with nil-error, returns
  4. If test(entries[1]) blocks, there are resource leaks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's strictly not necessary. I would just be forwarding the input context. If you are calling raceTests, you are also providing the context and function, so you can write the input function in a way that uses the input context.

That's exactly what I do in the stream_dialer.go code:

	resolver, err := raceTests(ctx, 250*time.Millisecond, resolvers, func(resolver *smartResolver) (*smartResolver, error) {
		for _, testDomain := range testDomains {
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			default:
			}
...

x/smart/stream_dialer.go Outdated Show resolved Hide resolved
x/smart/stream_dialer.go Outdated Show resolved Hide resolved
x/smart/stream_dialer.go Outdated Show resolved Hide resolved
x/smart/stream_dialer.go Outdated Show resolved Hide resolved
x/smart/stream_dialer.go Outdated Show resolved Hide resolved
fortuna and others added 4 commits February 22, 2024 09:44
Co-authored-by: J. Yi <93548144+jyyi1@users.noreply.github.com>
Co-authored-by: J. Yi <93548144+jyyi1@users.noreply.github.com>
Copy link
Contributor Author

@fortuna fortuna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I added a context to the DNS and TLS searches. I also added logCtx to make sure we don't output logs after the search is done, which is confusing.

I'm going to submit since you are out today and I'm going on vacation, but feel free to comment and I'll address any issue when I'm back.

@fortuna fortuna merged commit b7fe02b into main Feb 22, 2024
6 checks passed
@jyyi1
Copy link
Contributor

jyyi1 commented Feb 22, 2024

@fortuna Sounds good. One thing I just thought of is, maybe we can use container/list to implement the LRU cache since it might (at least in theory) be more efficient.

@fortuna
Copy link
Contributor Author

fortuna commented Feb 23, 2024 via email

@jyyi1
Copy link
Contributor

jyyi1 commented Feb 23, 2024

I considered that. While the move to front operation is O(1), you still need to scan the list. Scanning an array twice is probably faster than scanning a linked list once. We would have to combine the linked list with a map. It’s doable, but given the size of the list (100), I decided to punt on it, as the time is minimum compared with the network time.
…
On Thu, Feb 22, 2024 at 6:50β€―PM J. Yi @.> wrote: @fortuna https://github.com/fortuna Sounds good. One thing I just thought of is, maybe we can use container/list https://pkg.go.dev/container/list to implement the LRU cache since it might (at least in theory) be more efficient. β€” Reply to this email directly, view it on GitHub <#185 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAA3XHOGUJI7Y4XRVIGXFDTYU7KSTAVCNFSM6AAAAABDR5XRYOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSNRQGUZDMNBQHA . You are receiving this because you were mentioned.Message ID: @.>

Indeed, that's why I added "in theory". Scanning a continuous array is definitely faster than scanning the linked-list pointers scattered in the memory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants