From 24add0dce402868d9246bfd72001c453411c0bab Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 11 Mar 2021 04:57:07 +0300 Subject: [PATCH] Make network as a separae interface --- cli.go | 2 +- cli_access.go | 10 ++-- cli_generate_secret.go | 2 +- config.go | 4 +- example.config.toml | 12 +++-- mtglib/network/init.go | 15 +++++- mtglib/network/network.go | 98 ++++++++++++++++++++++----------------- utils.go | 10 ++-- 8 files changed, 90 insertions(+), 63 deletions(-) diff --git a/cli.go b/cli.go index 076b381..d57b159 100644 --- a/cli.go +++ b/cli.go @@ -8,8 +8,8 @@ import ( ) type cli struct { + network network.Network conf *config - network *network.Network } func (c *cli) ReadConfig(path string) error { diff --git a/cli_access.go b/cli_access.go index e969700..d78ffca 100644 --- a/cli_access.go +++ b/cli_access.go @@ -73,12 +73,10 @@ func (c *cliCommandAccess) Run(cli *CLI) error { } func (c *cliCommandAccess) getIP(protocol string) net.IP { - client := &http.Client{ - Timeout: c.network.HTTP.Timeout, - Transport: &http.Transport{ - DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { - return c.network.DialContext(ctx, protocol, address) - }, + client := c.network.MakeHTTPClient(0) + client.Transport = &http.Transport{ + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + return c.network.DialContext(ctx, protocol, address) }, } diff --git a/cli_generate_secret.go b/cli_generate_secret.go index 786e74c..41f8b04 100644 --- a/cli_generate_secret.go +++ b/cli_generate_secret.go @@ -7,7 +7,7 @@ import ( ) type cliCommandGenerateSecret struct { - cli + cli HostName string `arg optional help:"Hostname to use for domain fronting. Default is '${domain_front}'." name:"hostname" default:"${domain_front}"` // nolint: lll, govet Hex bool `help:"Print secret in hex encoding."` diff --git a/config.go b/config.go index e0c8442..b0c309a 100644 --- a/config.go +++ b/config.go @@ -412,7 +412,7 @@ type config struct { } `json:"public-ip"` Timeout struct { TCP configTypeDuration `json:"tcp"` - HTTP configTypeDuration `json:"http"` + Idle configTypeDuration `json:"idle"` } `json:"timeout"` DOHIP configTypeIP `json:"doh-ip"` Proxies []configTypeURL `json:"proxies"` @@ -478,7 +478,7 @@ type configRaw struct { } `toml:"public-ip" json:"public-ip"` Timeout struct { TCP string `toml:"tcp" json:"tcp"` - HTTP string `toml:"http" json:"http"` + Idle string `toml:"idle" json:"idle"` } `toml:"timeout" json:"timeout"` DOHIP string `toml:"doh-ip" json:"doh-ip"` Proxies []string `toml:"proxies" json:"proxies"` diff --git a/example.config.toml b/example.config.toml index 0f74c79..59bc550 100644 --- a/example.config.toml +++ b/example.config.toml @@ -101,11 +101,17 @@ proxies = [ ipv4 = "" ipv6 = "" -# network timeouts define different settings for timeouts. HTTP timeout -# is required only for DOH. +# network timeouts define different settings for timeouts. tcp timeout +# define a global timeout on establishing of network connections. idle +# means a timeout on pumping data between sockset when nothing is +# happening. +# +# please be noticed that handshakes have no timeouts intentionally. You can +# find a reasoning here: +# https://www.ndss-symposium.org/wp-content/uploads/2020/02/23087-paper.pdf [network.timeout] tcp = "5s" -http = "10s" +idle = "1m" # FakeTLS can compare timestamps to prevent probes. Each message has # encrypted timestamp. So, mtg can compare this timestamp and decide if diff --git a/mtglib/network/init.go b/mtglib/network/init.go index 593b935..d3aa8dc 100644 --- a/mtglib/network/init.go +++ b/mtglib/network/init.go @@ -4,13 +4,14 @@ import ( "context" "errors" "net" + "net/http" "time" ) const ( DefaultTimeout = 10 * time.Second - DefaultDNSTimeout = time.Second - DefaultHTTPTimeout = DefaultTimeout + DefaultIdleTimeout = time.Minute + DefaultHTTPTimeout = 5 * time.Second DefaultBufferSize = 4096 ProxyDialerOpenThreshold = 5 @@ -18,6 +19,8 @@ const ( ProxyDialerResetFailuresTimeout = 10 * time.Second DefaultDOHHostname = "9.9.9.9" + + DNSTimeout = 5 * time.Second ) var ( @@ -29,3 +32,11 @@ type Dialer interface { Dial(network, address string) (net.Conn, error) DialContext(ctx context.Context, network, address string) (net.Conn, error) } + +type Network interface { + Dialer + + DNSResolve(network, hostname string) (ips []string, err error) + MakeHTTPClient(timeout time.Duration) *http.Client + IdleTimeout() time.Duration +} diff --git a/mtglib/network/network.go b/mtglib/network/network.go index 31289ff..7d82878 100644 --- a/mtglib/network/network.go +++ b/mtglib/network/network.go @@ -12,21 +12,20 @@ import ( doh "github.com/babolivier/go-doh-client" ) -type Network struct { - HTTP http.Client - DNS doh.Resolver - - dialer Dialer +type network struct { + idleTimeout time.Duration + dialer Dialer + dns doh.Resolver } -func (d *Network) Dial(network, address string) (net.Conn, error) { - return d.DialContext(context.Background(), network, address) +func (n *network) Dial(protocol, address string) (net.Conn, error) { + return n.DialContext(context.Background(), protocol, address) } -func (d *Network) DialContext(ctx context.Context, network, address string) (net.Conn, error) { +func (n *network) DialContext(ctx context.Context, protocol, address string) (net.Conn, error) { host, port, _ := net.SplitHostPort(address) - ips, err := d.resolveIPs(network, host) + ips, err := n.DNSResolve(protocol, host) if err != nil { return nil, fmt.Errorf("cannot resolve dns names: %w", err) } @@ -37,16 +36,20 @@ func (d *Network) DialContext(ctx context.Context, network, address string) (net }) } + var conn net.Conn + for _, v := range ips { - if conn, err := d.dialer.DialContext(ctx, network, net.JoinHostPort(v, port)); err == nil { + conn, err = n.dialer.DialContext(ctx, protocol, net.JoinHostPort(v, port)) + + if err == nil { return conn, nil } } - return nil, fmt.Errorf("cannot dial to %s:%s", network, address) + return nil, fmt.Errorf("cannot dial to %s:%s: %w", protocol, address, err) } -func (d *Network) resolveIPs(network, address string) ([]string, error) { +func (n *network) DNSResolve(protocol, address string) ([]string, error) { if net.ParseIP(address) != nil { return []string{address}, nil } @@ -55,14 +58,14 @@ func (d *Network) resolveIPs(network, address string) ([]string, error) { wg := &sync.WaitGroup{} mutex := &sync.Mutex{} - switch network { + switch protocol { case "tcp", "tcp4": wg.Add(1) go func() { defer wg.Done() - if recs, _, err := d.DNS.LookupA(address); err == nil { + if recs, _, err := n.dns.LookupA(address); err == nil { mutex.Lock() defer mutex.Unlock() @@ -73,14 +76,14 @@ func (d *Network) resolveIPs(network, address string) ([]string, error) { }() } - switch network { + switch protocol { case "tcp", "tcp6": wg.Add(1) go func() { defer wg.Done() - if recs, _, err := d.DNS.LookupAAAA(address); err == nil { + if recs, _, err := n.dns.LookupAAAA(address); err == nil { mutex.Lock() defer mutex.Unlock() @@ -94,44 +97,53 @@ func (d *Network) resolveIPs(network, address string) ([]string, error) { wg.Wait() if len(ips) == 0 { - return nil, fmt.Errorf("cannot find any ips for %s:%s", network, address) + return nil, fmt.Errorf("cannot find any ips for %s:%s", protocol, address) } return ips, nil } -func NewNetwork(dialer Dialer, dohHostname string, httpTimeout time.Duration) (*Network, error) { +func (n *network) IdleTimeout() time.Duration { + return n.idleTimeout +} + +func (n *network) MakeHTTPClient(timeout time.Duration) *http.Client { + if timeout <= 0 { + timeout = DefaultHTTPTimeout + } + + return &http.Client{ + Timeout: timeout, + Transport: &http.Transport{ + DialContext: n.DialContext, + }, + } +} + +func NewNetwork(dialer Dialer, dohHostname string, idleTimeout time.Duration) (Network, error) { switch { - case httpTimeout < 0: - return nil, fmt.Errorf("timeout should be positive number %v", httpTimeout) - case httpTimeout == 0: - httpTimeout = DefaultHTTPTimeout + case idleTimeout < 0: + return nil, fmt.Errorf("timeout should be positive number %s", idleTimeout) + case idleTimeout == 0: + idleTimeout = DefaultIdleTimeout } if net.ParseIP(dohHostname) == nil { return nil, fmt.Errorf("hostname %s should be IP address", dohHostname) } - dohHTTPClient := &http.Client{ - Timeout: DefaultDNSTimeout, - Transport: &http.Transport{ - DialContext: dialer.DialContext, + return &network{ + dialer: dialer, + idleTimeout: idleTimeout, + dns: doh.Resolver{ + Host: dohHostname, + Class: doh.IN, + HTTPClient: &http.Client{ + Timeout: DNSTimeout, + Transport: &http.Transport{ + DialContext: dialer.DialContext, + }, + }, }, - } - network := &Network{ - dialer: dialer, - DNS: doh.Resolver{ - Host: dohHostname, - Class: doh.IN, - HTTPClient: dohHTTPClient, - }, - } - network.HTTP = http.Client{ - Timeout: httpTimeout, - Transport: &http.Transport{ - DialContext: network.DialContext, - }, - } - - return network, nil + }, nil } diff --git a/utils.go b/utils.go index 1c46d27..1411635 100644 --- a/utils.go +++ b/utils.go @@ -11,9 +11,9 @@ import ( "github.com/9seconds/mtg/v2/mtglib/network" ) -func makeNetwork(conf *config) (*network.Network, error) { +func makeNetwork(conf *config) (network.Network, error) { tcpTimeout := conf.Network.Timeout.TCP.Value(network.DefaultTimeout) - httpTimeout := conf.Network.Timeout.TCP.Value(network.DefaultHTTPTimeout) + idleTimeout := conf.Network.Timeout.Idle.Value(network.DefaultIdleTimeout) dohIP := conf.Network.DOHIP.Value(net.ParseIP(network.DefaultDOHHostname)).String() bufferSize := conf.TCPBuffer.Value(network.DefaultBufferSize) @@ -32,14 +32,14 @@ func makeNetwork(conf *config) (*network.Network, error) { switch len(proxyURLs) { case 0: - return network.NewNetwork(baseDialer, dohIP, httpTimeout) + return network.NewNetwork(baseDialer, dohIP, idleTimeout) case 1: socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0]) if err != nil { return nil, fmt.Errorf("cannot build socks5 dialer: %w", err) } - return network.NewNetwork(socksDialer, dohIP, httpTimeout) + return network.NewNetwork(socksDialer, dohIP, idleTimeout) } socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs) @@ -47,7 +47,7 @@ func makeNetwork(conf *config) (*network.Network, error) { return nil, fmt.Errorf("cannot build socks5 dialer: %w", err) } - return network.NewNetwork(socksDialer, dohIP, httpTimeout) + return network.NewNetwork(socksDialer, dohIP, idleTimeout) } func exhaustResponse(response *http.Response) {