Make network as a separae interface

This commit is contained in:
9seconds
2021-03-11 04:57:07 +03:00
parent 299c6478c2
commit 24add0dce4
8 changed files with 90 additions and 63 deletions
+1 -1
View File
@@ -8,8 +8,8 @@ import (
) )
type cli struct { type cli struct {
network network.Network
conf *config conf *config
network *network.Network
} }
func (c *cli) ReadConfig(path string) error { func (c *cli) ReadConfig(path string) error {
+4 -6
View File
@@ -73,12 +73,10 @@ func (c *cliCommandAccess) Run(cli *CLI) error {
} }
func (c *cliCommandAccess) getIP(protocol string) net.IP { func (c *cliCommandAccess) getIP(protocol string) net.IP {
client := &http.Client{ client := c.network.MakeHTTPClient(0)
Timeout: c.network.HTTP.Timeout, client.Transport = &http.Transport{
Transport: &http.Transport{ DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { return c.network.DialContext(ctx, protocol, address)
return c.network.DialContext(ctx, protocol, address)
},
}, },
} }
+1 -1
View File
@@ -7,7 +7,7 @@ import (
) )
type cliCommandGenerateSecret struct { 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 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."` Hex bool `help:"Print secret in hex encoding."`
+2 -2
View File
@@ -412,7 +412,7 @@ type config struct {
} `json:"public-ip"` } `json:"public-ip"`
Timeout struct { Timeout struct {
TCP configTypeDuration `json:"tcp"` TCP configTypeDuration `json:"tcp"`
HTTP configTypeDuration `json:"http"` Idle configTypeDuration `json:"idle"`
} `json:"timeout"` } `json:"timeout"`
DOHIP configTypeIP `json:"doh-ip"` DOHIP configTypeIP `json:"doh-ip"`
Proxies []configTypeURL `json:"proxies"` Proxies []configTypeURL `json:"proxies"`
@@ -478,7 +478,7 @@ type configRaw struct {
} `toml:"public-ip" json:"public-ip"` } `toml:"public-ip" json:"public-ip"`
Timeout struct { Timeout struct {
TCP string `toml:"tcp" json:"tcp"` TCP string `toml:"tcp" json:"tcp"`
HTTP string `toml:"http" json:"http"` Idle string `toml:"idle" json:"idle"`
} `toml:"timeout" json:"timeout"` } `toml:"timeout" json:"timeout"`
DOHIP string `toml:"doh-ip" json:"doh-ip"` DOHIP string `toml:"doh-ip" json:"doh-ip"`
Proxies []string `toml:"proxies" json:"proxies"` Proxies []string `toml:"proxies" json:"proxies"`
+9 -3
View File
@@ -101,11 +101,17 @@ proxies = [
ipv4 = "" ipv4 = ""
ipv6 = "" ipv6 = ""
# network timeouts define different settings for timeouts. HTTP timeout # network timeouts define different settings for timeouts. tcp timeout
# is required only for DOH. # 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] [network.timeout]
tcp = "5s" tcp = "5s"
http = "10s" idle = "1m"
# FakeTLS can compare timestamps to prevent probes. Each message has # FakeTLS can compare timestamps to prevent probes. Each message has
# encrypted timestamp. So, mtg can compare this timestamp and decide if # encrypted timestamp. So, mtg can compare this timestamp and decide if
+13 -2
View File
@@ -4,13 +4,14 @@ import (
"context" "context"
"errors" "errors"
"net" "net"
"net/http"
"time" "time"
) )
const ( const (
DefaultTimeout = 10 * time.Second DefaultTimeout = 10 * time.Second
DefaultDNSTimeout = time.Second DefaultIdleTimeout = time.Minute
DefaultHTTPTimeout = DefaultTimeout DefaultHTTPTimeout = 5 * time.Second
DefaultBufferSize = 4096 DefaultBufferSize = 4096
ProxyDialerOpenThreshold = 5 ProxyDialerOpenThreshold = 5
@@ -18,6 +19,8 @@ const (
ProxyDialerResetFailuresTimeout = 10 * time.Second ProxyDialerResetFailuresTimeout = 10 * time.Second
DefaultDOHHostname = "9.9.9.9" DefaultDOHHostname = "9.9.9.9"
DNSTimeout = 5 * time.Second
) )
var ( var (
@@ -29,3 +32,11 @@ type Dialer interface {
Dial(network, address string) (net.Conn, error) Dial(network, address string) (net.Conn, error)
DialContext(ctx context.Context, 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
}
+55 -43
View File
@@ -12,21 +12,20 @@ import (
doh "github.com/babolivier/go-doh-client" doh "github.com/babolivier/go-doh-client"
) )
type Network struct { type network struct {
HTTP http.Client idleTimeout time.Duration
DNS doh.Resolver dialer Dialer
dns doh.Resolver
dialer Dialer
} }
func (d *Network) Dial(network, address string) (net.Conn, error) { func (n *network) Dial(protocol, address string) (net.Conn, error) {
return d.DialContext(context.Background(), network, address) 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) host, port, _ := net.SplitHostPort(address)
ips, err := d.resolveIPs(network, host) ips, err := n.DNSResolve(protocol, host)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot resolve dns names: %w", err) 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 { 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 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 { if net.ParseIP(address) != nil {
return []string{address}, nil return []string{address}, nil
} }
@@ -55,14 +58,14 @@ func (d *Network) resolveIPs(network, address string) ([]string, error) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
mutex := &sync.Mutex{} mutex := &sync.Mutex{}
switch network { switch protocol {
case "tcp", "tcp4": case "tcp", "tcp4":
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
if recs, _, err := d.DNS.LookupA(address); err == nil { if recs, _, err := n.dns.LookupA(address); err == nil {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
@@ -73,14 +76,14 @@ func (d *Network) resolveIPs(network, address string) ([]string, error) {
}() }()
} }
switch network { switch protocol {
case "tcp", "tcp6": case "tcp", "tcp6":
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
if recs, _, err := d.DNS.LookupAAAA(address); err == nil { if recs, _, err := n.dns.LookupAAAA(address); err == nil {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
@@ -94,44 +97,53 @@ func (d *Network) resolveIPs(network, address string) ([]string, error) {
wg.Wait() wg.Wait()
if len(ips) == 0 { 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 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 { switch {
case httpTimeout < 0: case idleTimeout < 0:
return nil, fmt.Errorf("timeout should be positive number %v", httpTimeout) return nil, fmt.Errorf("timeout should be positive number %s", idleTimeout)
case httpTimeout == 0: case idleTimeout == 0:
httpTimeout = DefaultHTTPTimeout idleTimeout = DefaultIdleTimeout
} }
if net.ParseIP(dohHostname) == nil { if net.ParseIP(dohHostname) == nil {
return nil, fmt.Errorf("hostname %s should be IP address", dohHostname) return nil, fmt.Errorf("hostname %s should be IP address", dohHostname)
} }
dohHTTPClient := &http.Client{ return &network{
Timeout: DefaultDNSTimeout, dialer: dialer,
Transport: &http.Transport{ idleTimeout: idleTimeout,
DialContext: dialer.DialContext, dns: doh.Resolver{
Host: dohHostname,
Class: doh.IN,
HTTPClient: &http.Client{
Timeout: DNSTimeout,
Transport: &http.Transport{
DialContext: dialer.DialContext,
},
},
}, },
} }, nil
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
} }
+5 -5
View File
@@ -11,9 +11,9 @@ import (
"github.com/9seconds/mtg/v2/mtglib/network" "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) 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() dohIP := conf.Network.DOHIP.Value(net.ParseIP(network.DefaultDOHHostname)).String()
bufferSize := conf.TCPBuffer.Value(network.DefaultBufferSize) bufferSize := conf.TCPBuffer.Value(network.DefaultBufferSize)
@@ -32,14 +32,14 @@ func makeNetwork(conf *config) (*network.Network, error) {
switch len(proxyURLs) { switch len(proxyURLs) {
case 0: case 0:
return network.NewNetwork(baseDialer, dohIP, httpTimeout) return network.NewNetwork(baseDialer, dohIP, idleTimeout)
case 1: case 1:
socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0]) socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0])
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot build socks5 dialer: %w", err) 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) 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 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) { func exhaustResponse(response *http.Response) {