Add base dialers module

This commit is contained in:
9seconds
2021-03-05 12:20:57 +03:00
parent 2be900745f
commit 4689479745
12 changed files with 506 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
package dialers
import "time"
const (
DefaultTimeout = 10 * time.Second
DefaultHTTPTimeout = DefaultTimeout
DefaultBufferSize = 4096
)
+82
View File
@@ -0,0 +1,82 @@
package dialers
import (
"context"
"fmt"
"net"
"time"
"github.com/libp2p/go-reuseport"
)
type defaultBaseDialer struct {
net.Dialer
bufferSize int
}
func (d *defaultBaseDialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(context.Background(), network, address)
}
func (d *defaultBaseDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
switch network {
case "tcp", "tcp4", "tcp6":
default:
return nil, fmt.Errorf("unsupported network %s", network)
}
conn, err := d.Dialer.DialContext(ctx, network, address)
if err != nil {
return nil, fmt.Errorf("cannot dial to %s: %w", address, err)
}
tcpConn := conn.(*net.TCPConn)
if err := tcpConn.SetNoDelay(true); err != nil {
conn.Close()
return nil, fmt.Errorf("cannot set TCP_NO_DELAY: %w", err)
}
if err := tcpConn.SetReadBuffer(d.bufferSize); err != nil {
tcpConn.Close()
return nil, fmt.Errorf("cannot set read buffer size: %w", err)
}
if err := tcpConn.SetWriteBuffer(d.bufferSize); err != nil {
tcpConn.Close()
return nil, fmt.Errorf("cannot set write buffer size: %w", err)
}
if err := tcpConn.SetKeepAlive(true); err != nil {
tcpConn.Close()
return nil, fmt.Errorf("cannot enable keep-alive: %w", err)
}
return tcpConn, nil
}
func NewDefaultBaseDialer(timeout time.Duration, bufferSize int) (BaseDialer, error) {
switch {
case timeout < 0:
return nil, fmt.Errorf("timeout %v should be positive number", timeout)
case bufferSize < 0:
return nil, fmt.Errorf("buffer size %s should be positive number", bufferSize)
}
if timeout == 0 {
timeout = DefaultTimeout
}
if bufferSize == 0 {
bufferSize = DefaultBufferSize
}
return &defaultBaseDialer{
Dialer: net.Dialer{
Timeout: timeout,
Control: reuseport.Control,
},
bufferSize: bufferSize,
}, nil
}
+112
View File
@@ -0,0 +1,112 @@
package dialers
import (
"context"
"fmt"
"math/rand"
"net"
"net/http"
"time"
doh "github.com/babolivier/go-doh-client"
)
type Dialer struct {
HTTP http.Client
DNS doh.Resolver
baseDialer BaseDialer
}
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(context.Background(), network, address)
}
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
host, port, _ := net.SplitHostPort(address)
ips, err := d.resolveIPs(network, host)
if err != nil {
return nil, fmt.Errorf("cannot resolve dns names: %w", err)
}
rand.Shuffle(len(ips), func(i, j int) {
ips[i], ips[j] = ips[j], ips[i]
})
for _, v := range ips {
if conn, err := d.baseDialer.DialContext(ctx, network, net.JoinHostPort(v, port)); err == nil {
return conn, nil
}
}
return nil, fmt.Errorf("cannot dial to %s:%s", network, address)
}
func (d *Dialer) resolveIPs(network, address string) ([]string, error) {
if net.ParseIP(address) != nil {
return []string{address}, nil
}
var ips []string
switch network {
case "tcp", "tcp4":
if recs, _, err := d.DNS.LookupA(address); err == nil {
for _, v := range recs {
ips = append(ips, v.IP4)
}
}
}
switch network {
case "tcp", "tcp6":
if recs, _, err := d.DNS.LookupAAAA(address); err == nil {
for _, v := range recs {
ips = append(ips, v.IP6)
}
}
}
if len(ips) == 0 {
return nil, fmt.Errorf("cannot find any ips for %s:%s", network, address)
}
return ips, nil
}
func MakeDialer(base BaseDialer, dohHostname string, httpTimeout time.Duration) (*Dialer, error) {
switch {
case httpTimeout < 0:
return nil, fmt.Errorf("timeout should be positive number %v", httpTimeout)
case httpTimeout == 0:
httpTimeout = DefaultHTTPTimeout
}
if net.ParseIP(dohHostname) == nil {
return nil, fmt.Errorf("hostname %s should be IP address", dohHostname)
}
dohHTTPClient := &http.Client{
Timeout: httpTimeout,
Transport: &http.Transport{
DialContext: base.DialContext,
},
}
rv := &Dialer{
baseDialer: base,
DNS: doh.Resolver{
Host: dohHostname,
Class: doh.IN,
HTTPClient: dohHTTPClient,
},
}
rv.HTTP = http.Client{
Timeout: httpTimeout,
Transport: &http.Transport{
DialContext: rv.DialContext,
},
}
return rv, nil
}
+11
View File
@@ -0,0 +1,11 @@
package dialers
import (
"context"
"net"
)
type BaseDialer interface {
Dial(network, address string) (net.Conn, error)
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
+68
View File
@@ -0,0 +1,68 @@
package dialers
import (
"context"
"encoding/base64"
"fmt"
"net"
"net/url"
"strings"
"time"
shadowsocks "github.com/shadowsocks/go-shadowsocks2/core"
)
type shadowsocksBaseDialer struct {
base BaseDialer
cipher shadowsocks.StreamConnCipher
}
func (s *shadowsocksBaseDialer) Dial(network, address string) (net.Conn, error) {
conn, err := s.base.Dial(network, address)
if err != nil {
return nil, err
}
return s.cipher.StreamConn(conn), nil
}
func (s *shadowsocksBaseDialer) DialContext(ctx context.Context,
network, address string) (net.Conn, error) {
conn, err := s.base.DialContext(ctx, network, address)
if err != nil {
return nil, err
}
return s.cipher.StreamConn(conn), nil
}
func NewShadowsocksBaseDialer(proxyUrl *url.URL,
timeout time.Duration, bufferSize int) (BaseDialer, error) {
username := proxyUrl.User.Username()
decoded, err := base64.RawURLEncoding.DecodeString(username)
if err != nil {
return nil, fmt.Errorf("cannot decode payload: %w", err)
}
chunks := strings.SplitN(string(decoded), ":", 2)
if len(chunks) != 2 {
return nil, fmt.Errorf("incorrect payload %s", username)
}
cipher, err := shadowsocks.PickCipher(chunks[0], nil, chunks[1])
if err != nil {
return nil, fmt.Errorf("cannot initialize shadowsocks cipher: %w", err)
}
baseDialer, err := NewDefaultBaseDialer(timeout, bufferSize)
if err != nil {
return nil, fmt.Errorf("cannot initialize a base dialer: %w", err)
}
return &shadowsocksBaseDialer{
base: baseDialer,
cipher: cipher,
}, nil
}
+23
View File
@@ -0,0 +1,23 @@
package dialers
import (
"fmt"
"net/url"
"time"
"golang.org/x/net/proxy"
)
func NewSocks5BaseDialer(proxyUrl *url.URL, timeout time.Duration, bufferSize int) (BaseDialer, error) {
baseDialer, err := NewDefaultBaseDialer(timeout, bufferSize)
if err != nil {
return nil, fmt.Errorf("cannot initialize base dialer: %w", err)
}
rv, err := proxy.FromURL(proxyUrl, baseDialer.(*defaultBaseDialer))
if err != nil {
return nil, fmt.Errorf("cannot initialize socks5 proxy dialer: %w", err)
}
return rv.(BaseDialer), nil
}
+71
View File
@@ -0,0 +1,71 @@
package mtglib
import (
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"strings"
)
type Secret struct {
Key []byte
Host string
}
func (s *Secret) MarshalText() ([]byte, error) {
if s == nil {
return nil, nil
}
return []byte(s.String()), nil
}
func (s *Secret) UnmarshalText(text []byte) error {
sc, err := ParseSecret(string(text))
if err != nil {
return err
}
*s = sc
return nil
}
func (s Secret) Base64() string {
return s.String()
}
func (s Secret) EE() string {
return "ee" + hex.EncodeToString(append(s.Key, s.Host...))
}
func (s Secret) String() string {
return base64.StdEncoding.EncodeToString(append(s.Key, s.Host...))
}
func ParseSecret(secret string) (Secret, error) {
rv := Secret{}
if secret == "" {
return rv, errors.New("secret cannot be empty")
}
decoded, err := base64.RawStdEncoding.DecodeString(secret)
if err != nil && strings.HasPrefix(secret, "ee") {
decoded, err = hex.DecodeString(strings.TrimPrefix(secret, "ee"))
}
if err != nil {
return rv, fmt.Errorf("incorrect secret format: %w", err)
}
if len(decoded) < 33 {
return rv, fmt.Errorf("secret %s has incorrect length", secret)
}
rv.Key = decoded[:32]
rv.Host = string(decoded[32:])
return rv, nil
}