diff --git a/mtglib/init.go b/mtglib/init.go index 570cc1d..34e9e4e 100644 --- a/mtglib/init.go +++ b/mtglib/init.go @@ -27,9 +27,33 @@ const ( DefaultPreferIP = "prefer-ipv6" ) +// Network defines a knowledge how to work with a network. It may sound +// fun but it encapsulates all the knowledge how to properly establish +// connections to remote hosts and configure HTTP clients. +// +// For example, if you want to use SOCKS5 proxy, you probably want to +// have all traffic routed to this proxy: telegram connections, http +// requests and so on. This knowledge is encapsulated into instances of +// such interface. +// +// mtglib uses Network for: +// +// 1. Dialing to Telegram +// +// 2. Dialing to front domain +// +// 3. Doing HTTP requests (for example, for FireHOL ipblocklist). type Network interface { + // Dial establishes context-free TCP connections. Dial(network, address string) (net.Conn, error) + + // DialContext dials using a context. This is a preferrable + // way of establishing TCP connections. DialContext(ctx context.Context, network, address string) (net.Conn, error) + + // MakeHTTPClient build an HTTP client with given dial function. If + // nothing is provided, then DialContext of this interface is going + // to be used. MakeHTTPClient(func(ctx context.Context, network, address string) (net.Conn, error)) *http.Client } diff --git a/network/default.go b/network/default.go index 72b3da5..cad8799 100644 --- a/network/default.go +++ b/network/default.go @@ -60,6 +60,11 @@ func (d *defaultDialer) DialContext(ctx context.Context, network, address string return tcpConn, nil } +// NewDefaultDialer build a new dialer which dials bypassing proxies +// etc. +// +// The most default one you can imagine. But it has tunes TCP +// connections and setups SO_REUSEPORT. func NewDefaultDialer(timeout time.Duration, bufferSize int) (Dialer, error) { switch { case timeout < 0: diff --git a/network/init.go b/network/init.go index 57053af..0dc9660 100644 --- a/network/init.go +++ b/network/init.go @@ -1,3 +1,20 @@ +// Network contains a default implementation of the network. +// +// Please see mtglib.Network interface to get some basic idea behind +// this abstraction. +// +// Some notable feature of this implementation: +// +// 1. It detaches dialer from a network. Dialer is something which +// implements a real dialer and network completes it with more higher +// level details. +// +// 2. It uses only TCP connections. Even for DNS it uses DNS-Over-HTTPS +// +// 3. It has some simple implementation of DNS cache which is good +// enough for our purpose. +// +// 4. It sets uses SO_REUSEPORT port if applicable. package network import ( @@ -8,23 +25,59 @@ import ( ) const ( - DefaultTimeout = 10 * time.Second - DefaultHTTPTimeout = 10 * time.Second - DefaultBufferSize = 16 * 1024 // 16 kib + // DefaultTimeout is a default timeout for establishing TCP + // connection. + DefaultTimeout = 10 * time.Second + // DefaultHTTPTimeout defines a default timeout for making HTTP + // request. + DefaultHTTPTimeout = 10 * time.Second + + // DefaultBufferSize defines a TCP buffer size. Both read and write, so + // for real size, please multiply this number by 2. + DefaultBufferSize = 16 * 1024 // 16 kib + + // ProxyDialerOpenThreshold is used for load balancing SOCKS5 dialer + // only. + // + // This dialer uses circuit breaker with of 3 stages: OPEN, + // HALF_OPEN and CLOSED. If state is CLOSED, all requests go in + // a normal mode. If you get more that ProxyDialerOpenThreshold + // errors, circuit breaker goes into OPEN mode. + // + // When circuit breaker is in OPEN mode, it forbids all request to + // a given proxy. But after ProxyDialerHalfOpenTimeout it gives a + // second chance and opens an access for a SINGLE request. If this + // request success, then circuit breaker closes, otherwise opens + // again. + // + // When circuit breaker is closed, it clears an error states each + // ProxyDialerResetFailuresTimeout. ProxyDialerOpenThreshold = 5 ProxyDialerHalfOpenTimeout = time.Minute ProxyDialerResetFailuresTimeout = 10 * time.Second + // DefaultDOHHostname defines a default IP address for DOH host. + // Since mtg is simple, please pass IP address here. We do not + // have bootstrap servers here embedded. DefaultDOHHostname = "9.9.9.9" - DNSTimeout = 5 * time.Second + + // DNSTimeout defines a timeout for DNS queries. + DNSTimeout = 5 * time.Second ) var ( - ErrCircuitBreakerOpened = errors.New("circuit breaker is opened") + // ErrCircuitBreakerOpened is returned when proxy is being accessed + // but circuit breaker is opened. + ErrCircuitBreakerOpened = errors.New("circuit breaker is opened") + + // ErrCannotDialWithAllProxies is returned when load balancing + // client is trying to access proxies but all of them are failed. ErrCannotDialWithAllProxies = errors.New("cannot dial with all proxies") ) +// Dialer defines an interface which is required to bootstrap a network +// instance from. type Dialer interface { Dial(network, address string) (net.Conn, error) DialContext(ctx context.Context, network, address string) (net.Conn, error) diff --git a/network/load_balanced_socks5.go b/network/load_balanced_socks5.go index 2a60d69..a52004d 100644 --- a/network/load_balanced_socks5.go +++ b/network/load_balanced_socks5.go @@ -32,6 +32,16 @@ func (l loadBalancedSocks5Dialer) DialContext(ctx context.Context, network, addr return nil, ErrCannotDialWithAllProxies } +// NewLoadBalancedSocks5Dialer builds a new load balancing SOCKS5 +// dialer. +// +// The main difference from one which is made by NewSocks5Dialer is that +// we actually have a list of these proxies. When dial is requested, +// any proxy is picked and used. If proxy fails for some reason, we try +// another one. +// +// So, it is mostly useful if you have some routes with proxies which +// are not always online or having buggy network. func NewLoadBalancedSocks5Dialer(baseDialer Dialer, proxyURLs []*url.URL) (Dialer, error) { dialers := make([]Dialer, 0, len(proxyURLs)) diff --git a/network/network.go b/network/network.go index 147290b..3ff9af6 100644 --- a/network/network.go +++ b/network/network.go @@ -115,6 +115,10 @@ func (n *network) dnsResolve(protocol, address string) ([]string, error) { return ips, nil } +// NewNetwork assembles an mtglib.Network compatible structure +// based on a dialer and given params. +// +// It brings simple DNS cache and DNS-Over-HTTPS when necessary. func NewNetwork(dialer Dialer, userAgent, dohHostname string, httpTimeout time.Duration) (mtglib.Network, error) { diff --git a/network/socks5.go b/network/socks5.go index 91c9c61..43aac4a 100644 --- a/network/socks5.go +++ b/network/socks5.go @@ -7,6 +7,10 @@ import ( "golang.org/x/net/proxy" ) +// NewSocks5Dialer build a new dialer from a given one (so, in theory +// you can chain here). Proxy parameters are passed with URI in a form of: +// +// socks5://[user:[password]]@host:port func NewSocks5Dialer(baseDialer Dialer, proxyURL *url.URL) (Dialer, error) { rv, err := proxy.FromURL(proxyURL, baseDialer) if err != nil {