mirror of
https://github.com/Alexey71/opera-proxy.git
synced 2026-05-13 22:20:59 +00:00
split main package into subpackages
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
type FixedDialer struct {
|
||||
fixedAddress string
|
||||
next ContextDialer
|
||||
}
|
||||
|
||||
func NewFixedDialer(address string, next ContextDialer) *FixedDialer {
|
||||
return &FixedDialer{
|
||||
fixedAddress: address,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *FixedDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
_, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d.next.DialContext(ctx, network, net.JoinHostPort(d.fixedAddress, port))
|
||||
}
|
||||
|
||||
func (d *FixedDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
type Resolver struct {
|
||||
resolvers upstream.ParallelResolver
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewResolver(addresses []string, timeout time.Duration) (*Resolver, error) {
|
||||
resolvers := make([]upstream.Resolver, 0, len(addresses))
|
||||
opts := &upstream.Options{
|
||||
Timeout: timeout,
|
||||
}
|
||||
for _, addr := range addresses {
|
||||
u, err := upstream.AddressToUpstream(addr, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to construct upstream resolver from string %q: %w",
|
||||
addr, err)
|
||||
}
|
||||
resolvers = append(resolvers, &upstream.UpstreamResolver{Upstream: u})
|
||||
}
|
||||
return &Resolver{
|
||||
resolvers: resolvers,
|
||||
timeout: timeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Resolver) LookupNetIP(ctx context.Context, network string, host string) (addrs []netip.Addr, err error) {
|
||||
return r.resolvers.LookupNetIP(ctx, network, host)
|
||||
}
|
||||
|
||||
func (r *Resolver) Close() error {
|
||||
var res error
|
||||
for _, resolver := range r.resolvers {
|
||||
if closer, ok := resolver.(io.Closer); ok {
|
||||
if err := closer.Close(); err != nil {
|
||||
res = multierror.Append(res, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
PROXY_CONNECT_METHOD = "CONNECT"
|
||||
PROXY_HOST_HEADER = "Host"
|
||||
PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization"
|
||||
MISSING_CHAIN_CERT = `-----BEGIN CERTIFICATE-----
|
||||
MIID0zCCArugAwIBAgIQVmcdBOpPmUxvEIFHWdJ1lDANBgkqhkiG9w0BAQwFADB7
|
||||
MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
|
||||
VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE
|
||||
AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4
|
||||
MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5
|
||||
MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO
|
||||
ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgRUNDIENlcnRpZmljYXRpb24gQXV0
|
||||
aG9yaXR5MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEGqxUWqn5aCPnetUkb1PGWthL
|
||||
q8bVttHmc3Gu3ZzWDGH926CJA7gFFOxXzu5dP+Ihs8731Ip54KODfi2X0GHE8Znc
|
||||
JZFjq38wo7Rw4sehM5zzvy5cU7Ffs30yf4o043l5o4HyMIHvMB8GA1UdIwQYMBaA
|
||||
FKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1
|
||||
xmNjmjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAI
|
||||
MAYGBFUdIAAwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5j
|
||||
b20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQG
|
||||
CCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEM
|
||||
BQADggEBABns652JLCALBIAdGN5CmXKZFjK9Dpx1WywV4ilAbe7/ctvbq5AfjJXy
|
||||
ij0IckKJUAfiORVsAYfZFhr1wHUrxeZWEQff2Ji8fJ8ZOd+LygBkc7xGEJuTI42+
|
||||
FsMuCIKchjN0djsoTI0DQoWz4rIjQtUfenVqGtF8qmchxDM6OW1TyaLtYiKou+JV
|
||||
bJlsQ2uRl9EMC5MCHdK8aXdJ5htN978UeAOwproLtOGFfy/cQjutdAFI3tZs4RmY
|
||||
CV4Ks2dH/hzg1cEo70qLRDEmBDeNiXQ2Lu+lIg+DdEmSx/cQwgwp+7e9un/jX9Wf
|
||||
8qn0dNW44bOwgeThpWOjzOoEeJBuv/c=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
)
|
||||
|
||||
var missingLinkDER, _ = pem.Decode([]byte(MISSING_CHAIN_CERT))
|
||||
var missingLink, _ = x509.ParseCertificate(missingLinkDER.Bytes)
|
||||
|
||||
type stringCb = func() (string, error)
|
||||
|
||||
type Dialer interface {
|
||||
Dial(network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
type ContextDialer interface {
|
||||
Dialer
|
||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
type ProxyDialer struct {
|
||||
address stringCb
|
||||
tlsServerName stringCb
|
||||
auth stringCb
|
||||
next ContextDialer
|
||||
intermediateWorkaround bool
|
||||
caPool *x509.CertPool
|
||||
}
|
||||
|
||||
func NewProxyDialer(address, tlsServerName, auth stringCb, intermediateWorkaround bool, caPool *x509.CertPool, nextDialer ContextDialer) *ProxyDialer {
|
||||
return &ProxyDialer{
|
||||
address: address,
|
||||
tlsServerName: tlsServerName,
|
||||
auth: auth,
|
||||
next: nextDialer,
|
||||
intermediateWorkaround: intermediateWorkaround,
|
||||
caPool: caPool,
|
||||
}
|
||||
}
|
||||
|
||||
func ProxyDialerFromURL(u *url.URL, next ContextDialer) (*ProxyDialer, error) {
|
||||
host := u.Hostname()
|
||||
port := u.Port()
|
||||
tlsServerName := ""
|
||||
var auth stringCb = nil
|
||||
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http":
|
||||
if port == "" {
|
||||
port = "80"
|
||||
}
|
||||
case "https":
|
||||
if port == "" {
|
||||
port = "443"
|
||||
}
|
||||
tlsServerName = host
|
||||
default:
|
||||
return nil, errors.New("unsupported proxy type")
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(host, port)
|
||||
|
||||
if u.User != nil {
|
||||
username := u.User.Username()
|
||||
password, _ := u.User.Password()
|
||||
auth = WrapStringToCb(BasicAuthHeader(username, password))
|
||||
}
|
||||
return NewProxyDialer(WrapStringToCb(address), WrapStringToCb(tlsServerName), auth, false, nil, next), nil
|
||||
}
|
||||
|
||||
func (d *ProxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
default:
|
||||
return nil, errors.New("bad network specified for DialContext: only tcp is supported")
|
||||
}
|
||||
|
||||
uAddress, err := d.address()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := d.next.DialContext(ctx, "tcp", uAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uTLSServerName, err := d.tlsServerName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uTLSServerName != "" {
|
||||
// Custom cert verification logic:
|
||||
// DO NOT send SNI extension of TLS ClientHello
|
||||
// DO peer certificate verification against specified servername
|
||||
conn = tls.Client(conn, &tls.Config{
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: true,
|
||||
VerifyConnection: func(cs tls.ConnectionState) error {
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: uTLSServerName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
Roots: d.caPool,
|
||||
}
|
||||
waRequired := false
|
||||
for _, cert := range cs.PeerCertificates[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
if d.intermediateWorkaround && !waRequired &&
|
||||
bytes.Compare(cert.AuthorityKeyId, missingLink.SubjectKeyId) == 0 {
|
||||
waRequired = true
|
||||
}
|
||||
}
|
||||
if waRequired {
|
||||
opts.Intermediates.AddCert(missingLink)
|
||||
}
|
||||
_, err := cs.PeerCertificates[0].Verify(opts)
|
||||
return err
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Method: PROXY_CONNECT_METHOD,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
RequestURI: address,
|
||||
Host: address,
|
||||
Header: http.Header{
|
||||
PROXY_HOST_HEADER: []string{address},
|
||||
},
|
||||
}
|
||||
|
||||
if d.auth != nil {
|
||||
auth, err := d.auth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set(PROXY_AUTHORIZATION_HEADER, auth)
|
||||
}
|
||||
|
||||
rawreq, err := httputil.DumpRequest(req, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = conn.Write(rawreq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyResp, err := readResponse(conn, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if proxyResp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(fmt.Sprintf("bad response from upstream proxy server: %s", proxyResp.Status))
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (d *ProxyDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func readResponse(r io.Reader, req *http.Request) (*http.Response, error) {
|
||||
endOfResponse := []byte("\r\n\r\n")
|
||||
buf := &bytes.Buffer{}
|
||||
b := make([]byte, 1)
|
||||
for {
|
||||
n, err := r.Read(b)
|
||||
if n < 1 && err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
buf.Write(b)
|
||||
sl := buf.Bytes()
|
||||
if len(sl) < len(endOfResponse) {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(sl[len(sl)-4:], endOfResponse) {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return http.ReadResponse(bufio.NewReader(buf), req)
|
||||
}
|
||||
|
||||
func BasicAuthHeader(login, password string) string {
|
||||
return "Basic " + base64.StdEncoding.EncodeToString(
|
||||
[]byte(login+":"+password))
|
||||
}
|
||||
|
||||
func WrapStringToCb(s string) func() (string, error) {
|
||||
return func() (string, error) {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user