Skip to content

Commit d274406

Browse files
authored
Merge pull request #111 from SenseUnit/dialer_cache
Dialer cache
2 parents d50ed81 + 830a88b commit d274406

File tree

3 files changed

+97
-10
lines changed

3 files changed

+97
-10
lines changed

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ Open Firefox proxy settings, switch proxy mode to "Automatic proxy configuration
174174
data:,function FindProxyForURL(u, h){return "HTTPS example.com:8080";}
175175
```
176176

177-
![ff_https_proxy](https://user-images.githubusercontent.com/3524671/82768442-afea9e00-9e37-11ea-80fd-1eccf55b89fa.png)
177+
![ff\_https\_proxy](https://user-images.githubusercontent.com/3524671/82768442-afea9e00-9e37-11ea-80fd-1eccf55b89fa.png)
178178

179179
#### Option 2. Browser extension.
180180

@@ -196,7 +196,17 @@ Use any proxy switching browser extension which supports HTTPS proxies like [thi
196196

197197
### Using with other applications
198198

199-
It is possible to expose remote HTTPS proxy as a local plaintext HTTP proxy with help of external application which performs remote communication via TLS and exposes local plaintext socket. [steady-tun](https://github.com/Snawoot/steady-tun) appears to be most suitable for this because it supports connection pooling to hide connection delay.
199+
It is possible to expose remote HTTPS proxy as a local plaintext HTTP proxy with the help of some application which performs remote communication via TLS and exposes local plaintext socket. dumbproxy itself can play this role and use upstream proxy to provide local proxy service. For example, command
200+
201+
```
202+
dumbproxy -bind-address 127.0.0.1:8080 -proxy 'https://login:[email protected]'
203+
```
204+
205+
would expose remote HTTPS proxy at example.org:443 with `login` and `password` on local port 8080 as a regular HTTP proxy without authentication. Or, if you prefer mTLS authentication, it would be
206+
207+
```
208+
dumbproxy -bind-address 127.0.0.1:8080 -proxy 'https://example.org?cert=cert.pem&key=key.pem&cafile=ca.pem'
209+
```
200210

201211
### Using with Android
202212

@@ -332,6 +342,9 @@ Supported proxy schemes are:
332342
* `max-tls-version` - maximum TLS version.
333343
* `socks5`, `socks5h` - SOCKS5 proxy with hostname resolving via remote proxy. Example: `socks5://127.0.0.1:9050`.
334344
* `set-src-hints` - not an actual proxy, but a signal to use different source IP address hints for this connection. It's useful to route traffic across multiple network interfaces, including VPN connections. URL has to have one query parameter `hints` with a comma-separated list of IP addresses. See `-ip-hints` command line option for more details. Example: `set-src-hints://?hints=10.2.0.2`
345+
* `cached` - pseudo-dialer which caches construction of another dialer specified by URL passed in `url` parameter of query string. Useful for dialers which are constructed dynamically from JS router script and which load certificate files. Example: `cache://?url=https%3A%2F%2Fexample.org%3Fcert%3Dcert.pem%26key%3Dkey.pem&ttl=5m`. Query string parameters are:
346+
* `url` - actual proxy URL. Note that just like any query string parameter this one has to be URL-encoded to be passed as query string value.
347+
* `ttl` - time to live for cache record. Examples: `15s`, `2m`, `1h`.
335348

336349
## Synopsis
337350

dialer/cache.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package dialer
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/url"
7+
"time"
8+
9+
"github.com/jellydator/ttlcache/v3"
10+
xproxy "golang.org/x/net/proxy"
11+
"golang.org/x/sync/singleflight"
12+
)
13+
14+
type dialerCacheKey struct {
15+
url string
16+
next xproxy.Dialer
17+
}
18+
19+
type dialerCacheValue struct {
20+
dialer xproxy.Dialer
21+
err error
22+
}
23+
24+
var (
25+
dialerCache = ttlcache.New[dialerCacheKey, dialerCacheValue](
26+
ttlcache.WithDisableTouchOnHit[dialerCacheKey, dialerCacheValue](),
27+
)
28+
dialerCacheSingleFlight = new(singleflight.Group)
29+
)
30+
31+
func GetCachedDialer(u *url.URL, next xproxy.Dialer) (xproxy.Dialer, error) {
32+
params, err := url.ParseQuery(u.RawQuery)
33+
if err != nil {
34+
return nil, err
35+
}
36+
if !params.Has("url") {
37+
return nil, errors.New("cached dialer: no \"url\" parameter specified")
38+
}
39+
parsedURL, err := url.Parse(params.Get("url"))
40+
if err != nil {
41+
return nil, fmt.Errorf("unable to parse proxy URL: %w", err)
42+
}
43+
if !params.Has("ttl") {
44+
return nil, errors.New("cached dialer: no \"ttl\" parameter specified")
45+
}
46+
ttl, err := time.ParseDuration(params.Get("ttl"))
47+
if err != nil {
48+
return nil, fmt.Errorf("cached dialer: unable to parse TTL duration %q: %w", params.Get("ttl"), err)
49+
}
50+
cacheRes := dialerCache.Get(
51+
dialerCacheKey{
52+
url: params.Get("url"),
53+
next: next,
54+
},
55+
ttlcache.WithLoader[dialerCacheKey, dialerCacheValue](
56+
ttlcache.NewSuppressedLoader[dialerCacheKey, dialerCacheValue](
57+
ttlcache.LoaderFunc[dialerCacheKey, dialerCacheValue](
58+
func(c *ttlcache.Cache[dialerCacheKey, dialerCacheValue], key dialerCacheKey) *ttlcache.Item[dialerCacheKey, dialerCacheValue] {
59+
dialer, err := xproxy.FromURL(parsedURL, next)
60+
return c.Set(
61+
key,
62+
dialerCacheValue{
63+
dialer: dialer,
64+
err: err,
65+
},
66+
ttl,
67+
)
68+
},
69+
),
70+
dialerCacheSingleFlight,
71+
),
72+
),
73+
).Value()
74+
return cacheRes.dialer, cacheRes.err
75+
}

dialer/dialer.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ import (
55
"fmt"
66
"net"
77
"net/url"
8-
"sync"
98

109
xproxy "golang.org/x/net/proxy"
1110
)
1211

12+
func init() {
13+
xproxy.RegisterDialerType("http", HTTPProxyDialerFromURL)
14+
xproxy.RegisterDialerType("https", HTTPProxyDialerFromURL)
15+
xproxy.RegisterDialerType("set-src-hints", NewHintsSettingDialerFromURL)
16+
xproxy.RegisterDialerType("cached", GetCachedDialer)
17+
}
18+
1319
type LegacyDialer interface {
1420
Dial(network, address string) (net.Conn, error)
1521
}
@@ -19,14 +25,7 @@ type Dialer interface {
1925
DialContext(ctx context.Context, network, address string) (net.Conn, error)
2026
}
2127

22-
var registerDialerTypesOnce sync.Once
23-
2428
func ProxyDialerFromURL(proxyURL string, forward Dialer) (Dialer, error) {
25-
registerDialerTypesOnce.Do(func() {
26-
xproxy.RegisterDialerType("http", HTTPProxyDialerFromURL)
27-
xproxy.RegisterDialerType("https", HTTPProxyDialerFromURL)
28-
xproxy.RegisterDialerType("set-src-hints", NewHintsSettingDialerFromURL)
29-
})
3029
parsedURL, err := url.Parse(proxyURL)
3130
if err != nil {
3231
return nil, fmt.Errorf("unable to parse proxy URL: %w", err)

0 commit comments

Comments
 (0)