~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
160 lines · 4.0 KB Raw History
package networkdial

import (
	"cmp"
	"context"
	"crypto/tls"
	"fmt"
	"net"
	"net/http"
	"time"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/nettransport"
)

const (
	DefaultDialTimeout      = 10 * time.Second
	DefaultKeepAlive        = 30 * time.Second
	DefaultHTTPTimeout      = 30 * time.Second
	DefaultIdleConnTimeout  = 90 * time.Second
	DefaultHandshakeTimeout = 15 * time.Second
	DefaultMaxIdleConns     = 100
)

type DialOptions struct {
	Hosts          Resolver
	TLSConfig      *tls.Config
	AllowSystemDNS bool
}

type Dialer struct {
	resolver       Resolver
	tlsConfig      *tls.Config
	dialer         net.Dialer
	allowSystemDNS bool
}

func NewDialer(hosts Resolver, tlsConfig *tls.Config) (*Dialer, error) {
	return NewDialerWithOptions(DialOptions{Hosts: hosts, TLSConfig: tlsConfig})
}

func NewDialerWithOptions(opts DialOptions) (*Dialer, error) {
	if opts.Hosts == nil {
		return nil, ErrNoResolver
	}
	return &Dialer{
		resolver:  opts.Hosts,
		tlsConfig: cloneTLSConfig(opts.TLSConfig),
		dialer: net.Dialer{
			Timeout:   DefaultDialTimeout,
			KeepAlive: DefaultKeepAlive,
		},
		allowSystemDNS: opts.AllowSystemDNS,
	}, nil
}

func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
	if d == nil || d.resolver == nil {
		return nil, ErrNoResolver
	}
	target, err := d.address(address)
	if err != nil {
		return nil, err
	}
	conn, err := d.dialer.DialContext(ctx, network, target)
	if err != nil {
		return nil, fmt.Errorf("networkdial: dialing %s for %s: %w", target, address, err)
	}
	return conn, nil
}

func (d *Dialer) Resolve(address string) (string, error) {
	if d == nil || d.resolver == nil {
		return "", ErrNoResolver
	}
	return d.address(address)
}

func (d *Dialer) TLSConfig() *tls.Config {
	if d == nil {
		return cloneTLSConfig(nil)
	}
	return cloneTLSConfig(d.tlsConfig)
}

func (d *Dialer) address(address string) (string, error) {
	host, _, err := net.SplitHostPort(address)
	if err != nil {
		return "", fmt.Errorf("networkdial: %q is not host:port: %w", address, err)
	}
	if mapped, ok := d.resolver.Address(address); ok {
		return mapped, nil
	}
	if net.ParseIP(host) != nil {
		return address, nil
	}
	if d.allowSystemDNS {
		return address, nil
	}
	return "", fmt.Errorf("%w: %w: %s", ErrUnknownHost, ErrSystemDNSBlocked, address)
}

func NewHTTPTransport(hosts Resolver, tlsConfig *tls.Config) (*http.Transport, error) {
	return NewHTTPTransportWithOptions(DialOptions{Hosts: hosts, TLSConfig: tlsConfig})
}

func NewHTTPTransportWithOptions(opts DialOptions) (*http.Transport, error) {
	d, err := NewDialerWithOptions(opts)
	if err != nil {
		return nil, err
	}
	return nettransport.WithDial(&http.Transport{
		TLSClientConfig:       d.TLSConfig(),
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          DefaultMaxIdleConns,
		MaxIdleConnsPerHost:   10,
		IdleConnTimeout:       DefaultIdleConnTimeout,
		TLSHandshakeTimeout:   DefaultHandshakeTimeout,
		ExpectContinueTimeout: time.Second,
	}, d.DialContext), nil
}

func NewHTTPClient(hosts Resolver, tlsConfig *tls.Config) (*http.Client, error) {
	return NewHTTPClientWithOptions(DialOptions{Hosts: hosts, TLSConfig: tlsConfig})
}

func NewHTTPClientWithOptions(opts DialOptions) (*http.Client, error) {
	tr, err := NewHTTPTransportWithOptions(opts)
	if err != nil {
		return nil, err
	}
	return &http.Client{
		Transport: tr,
		Timeout:   DefaultHTTPTimeout,
	}, nil
}

func NewNetworkDialer(hosts Resolver, caBundlePEM []byte) (*Dialer, error) {
	cfg, err := TLSConfigFromCA(caBundlePEM, "")
	if err != nil {
		return nil, err
	}
	return NewDialerWithOptions(DialOptions{Hosts: hosts, TLSConfig: cfg})
}

func NewNetworkHTTPClient(hosts Resolver, caBundlePEM []byte) (*http.Client, error) {
	cfg, err := TLSConfigFromCA(caBundlePEM, "")
	if err != nil {
		return nil, err
	}
	return NewHTTPClientWithOptions(DialOptions{Hosts: hosts, TLSConfig: cfg})
}

func cloneTLSConfig(cfg *tls.Config) *tls.Config {
	if cfg == nil {
		return &tls.Config{MinVersion: tls.VersionTLS12}
	}
	out := cfg.Clone()
	out.MinVersion = cmp.Or(out.MinVersion, tls.VersionTLS12)
	return out
}