github.com/metacubex/mihomo@v1.18.5/adapter/outbound/snell.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"strconv"
     8  
     9  	N "github.com/metacubex/mihomo/common/net"
    10  	"github.com/metacubex/mihomo/common/structure"
    11  	"github.com/metacubex/mihomo/component/dialer"
    12  	"github.com/metacubex/mihomo/component/proxydialer"
    13  	C "github.com/metacubex/mihomo/constant"
    14  	obfs "github.com/metacubex/mihomo/transport/simple-obfs"
    15  	"github.com/metacubex/mihomo/transport/snell"
    16  )
    17  
    18  type Snell struct {
    19  	*Base
    20  	option     *SnellOption
    21  	psk        []byte
    22  	pool       *snell.Pool
    23  	obfsOption *simpleObfsOption
    24  	version    int
    25  }
    26  
    27  type SnellOption struct {
    28  	BasicOption
    29  	Name     string         `proxy:"name"`
    30  	Server   string         `proxy:"server"`
    31  	Port     int            `proxy:"port"`
    32  	Psk      string         `proxy:"psk"`
    33  	UDP      bool           `proxy:"udp,omitempty"`
    34  	Version  int            `proxy:"version,omitempty"`
    35  	ObfsOpts map[string]any `proxy:"obfs-opts,omitempty"`
    36  }
    37  
    38  type streamOption struct {
    39  	psk        []byte
    40  	version    int
    41  	addr       string
    42  	obfsOption *simpleObfsOption
    43  }
    44  
    45  func streamConn(c net.Conn, option streamOption) *snell.Snell {
    46  	switch option.obfsOption.Mode {
    47  	case "tls":
    48  		c = obfs.NewTLSObfs(c, option.obfsOption.Host)
    49  	case "http":
    50  		_, port, _ := net.SplitHostPort(option.addr)
    51  		c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port)
    52  	}
    53  	return snell.StreamConn(c, option.psk, option.version)
    54  }
    55  
    56  // StreamConnContext implements C.ProxyAdapter
    57  func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
    58  	c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
    59  	if metadata.NetWork == C.UDP {
    60  		err := snell.WriteUDPHeader(c, s.version)
    61  		return c, err
    62  	}
    63  	err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
    64  	return c, err
    65  }
    66  
    67  // DialContext implements C.ProxyAdapter
    68  func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
    69  	if s.version == snell.Version2 && len(opts) == 0 {
    70  		c, err := s.pool.Get()
    71  		if err != nil {
    72  			return nil, err
    73  		}
    74  
    75  		if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil {
    76  			c.Close()
    77  			return nil, err
    78  		}
    79  		return NewConn(c, s), err
    80  	}
    81  
    82  	return s.DialContextWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata)
    83  }
    84  
    85  // DialContextWithDialer implements C.ProxyAdapter
    86  func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
    87  	if len(s.option.DialerProxy) > 0 {
    88  		dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer)
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  	}
    93  	c, err := dialer.DialContext(ctx, "tcp", s.addr)
    94  	if err != nil {
    95  		return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
    96  	}
    97  	N.TCPKeepAlive(c)
    98  
    99  	defer func(c net.Conn) {
   100  		safeConnClose(c, err)
   101  	}(c)
   102  
   103  	c, err = s.StreamConnContext(ctx, c, metadata)
   104  	return NewConn(c, s), err
   105  }
   106  
   107  // ListenPacketContext implements C.ProxyAdapter
   108  func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
   109  	return s.ListenPacketWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata)
   110  }
   111  
   112  // ListenPacketWithDialer implements C.ProxyAdapter
   113  func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
   114  	var err error
   115  	if len(s.option.DialerProxy) > 0 {
   116  		dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  	}
   121  	c, err := dialer.DialContext(ctx, "tcp", s.addr)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	N.TCPKeepAlive(c)
   126  	c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
   127  
   128  	err = snell.WriteUDPHeader(c, s.version)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	pc := snell.PacketConn(c)
   134  	return newPacketConn(pc, s), nil
   135  }
   136  
   137  // SupportWithDialer implements C.ProxyAdapter
   138  func (s *Snell) SupportWithDialer() C.NetWork {
   139  	return C.ALLNet
   140  }
   141  
   142  // SupportUOT implements C.ProxyAdapter
   143  func (s *Snell) SupportUOT() bool {
   144  	return true
   145  }
   146  
   147  func NewSnell(option SnellOption) (*Snell, error) {
   148  	addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
   149  	psk := []byte(option.Psk)
   150  
   151  	decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
   152  	obfsOption := &simpleObfsOption{Host: "bing.com"}
   153  	if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
   154  		return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
   155  	}
   156  
   157  	switch obfsOption.Mode {
   158  	case "tls", "http", "":
   159  		break
   160  	default:
   161  		return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
   162  	}
   163  
   164  	// backward compatible
   165  	if option.Version == 0 {
   166  		option.Version = snell.DefaultSnellVersion
   167  	}
   168  	switch option.Version {
   169  	case snell.Version1, snell.Version2:
   170  		if option.UDP {
   171  			return nil, fmt.Errorf("snell version %d not support UDP", option.Version)
   172  		}
   173  	case snell.Version3:
   174  	default:
   175  		return nil, fmt.Errorf("snell version error: %d", option.Version)
   176  	}
   177  
   178  	s := &Snell{
   179  		Base: &Base{
   180  			name:   option.Name,
   181  			addr:   addr,
   182  			tp:     C.Snell,
   183  			udp:    option.UDP,
   184  			tfo:    option.TFO,
   185  			mpTcp:  option.MPTCP,
   186  			iface:  option.Interface,
   187  			rmark:  option.RoutingMark,
   188  			prefer: C.NewDNSPrefer(option.IPVersion),
   189  		},
   190  		option:     &option,
   191  		psk:        psk,
   192  		obfsOption: obfsOption,
   193  		version:    option.Version,
   194  	}
   195  
   196  	if option.Version == snell.Version2 {
   197  		s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
   198  			var err error
   199  			var cDialer C.Dialer = dialer.NewDialer(s.Base.DialOptions()...)
   200  			if len(s.option.DialerProxy) > 0 {
   201  				cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer)
   202  				if err != nil {
   203  					return nil, err
   204  				}
   205  			}
   206  			c, err := cDialer.DialContext(ctx, "tcp", addr)
   207  			if err != nil {
   208  				return nil, err
   209  			}
   210  
   211  			N.TCPKeepAlive(c)
   212  			return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
   213  		})
   214  	}
   215  	return s, nil
   216  }