123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- package outbound
- import (
- "context"
- "crypto/tls"
- "errors"
- "fmt"
- "net"
- "runtime"
- "strconv"
- "time"
- CN "github.com/metacubex/mihomo/common/net"
- "github.com/metacubex/mihomo/common/utils"
- "github.com/metacubex/mihomo/component/ca"
- "github.com/metacubex/mihomo/component/dialer"
- "github.com/metacubex/mihomo/component/proxydialer"
- C "github.com/metacubex/mihomo/constant"
- "github.com/metacubex/mihomo/log"
- tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
- "github.com/metacubex/sing-quic/hysteria2"
- "github.com/metacubex/randv2"
- M "github.com/sagernet/sing/common/metadata"
- )
- func init() {
- hysteria2.SetCongestionController = tuicCommon.SetCongestionController
- }
- const minHopInterval = 5
- const defaultHopInterval = 30
- type Hysteria2 struct {
- *Base
- option *Hysteria2Option
- client *hysteria2.Client
- dialer proxydialer.SingDialer
- }
- type Hysteria2Option struct {
- BasicOption
- Name string `proxy:"name"`
- Server string `proxy:"server"`
- Port int `proxy:"port,omitempty"`
- Ports string `proxy:"ports,omitempty"`
- HopInterval int `proxy:"hop-interval,omitempty"`
- Up string `proxy:"up,omitempty"`
- Down string `proxy:"down,omitempty"`
- Password string `proxy:"password,omitempty"`
- Obfs string `proxy:"obfs,omitempty"`
- ObfsPassword string `proxy:"obfs-password,omitempty"`
- SNI string `proxy:"sni,omitempty"`
- SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
- Fingerprint string `proxy:"fingerprint,omitempty"`
- ALPN []string `proxy:"alpn,omitempty"`
- CustomCA string `proxy:"ca,omitempty"`
- CustomCAString string `proxy:"ca-str,omitempty"`
- CWND int `proxy:"cwnd,omitempty"`
- UdpMTU int `proxy:"udp-mtu,omitempty"`
- }
- func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
- options := h.Base.DialOptions(opts...)
- h.dialer.SetDialer(dialer.NewDialer(options...))
- c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
- if err != nil {
- return nil, err
- }
- return NewConn(CN.NewRefConn(c, h), h), nil
- }
- func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
- options := h.Base.DialOptions(opts...)
- h.dialer.SetDialer(dialer.NewDialer(options...))
- pc, err := h.client.ListenPacket(ctx)
- if err != nil {
- return nil, err
- }
- if pc == nil {
- return nil, errors.New("packetConn is nil")
- }
- return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), h), h), nil
- }
- func closeHysteria2(h *Hysteria2) {
- if h.client != nil {
- _ = h.client.CloseWithError(errors.New("proxy removed"))
- }
- }
- func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
- addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
- var salamanderPassword string
- if len(option.Obfs) > 0 {
- if option.ObfsPassword == "" {
- return nil, errors.New("missing obfs password")
- }
- switch option.Obfs {
- case hysteria2.ObfsTypeSalamander:
- salamanderPassword = option.ObfsPassword
- default:
- return nil, fmt.Errorf("unknown obfs type: %s", option.Obfs)
- }
- }
- serverName := option.Server
- if option.SNI != "" {
- serverName = option.SNI
- }
- tlsConfig := &tls.Config{
- ServerName: serverName,
- InsecureSkipVerify: option.SkipCertVerify,
- MinVersion: tls.VersionTLS13,
- }
- var err error
- tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
- if err != nil {
- return nil, err
- }
- if len(option.ALPN) > 0 {
- tlsConfig.NextProtos = option.ALPN
- }
- if option.UdpMTU == 0 {
- // "1200" from quic-go's MaxDatagramSize
- // "-3" from quic-go's DatagramFrame.MaxDataLen
- option.UdpMTU = 1200 - 3
- }
- singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
- clientOptions := hysteria2.ClientOptions{
- Context: context.TODO(),
- Dialer: singDialer,
- Logger: log.SingLogger,
- SendBPS: StringToBps(option.Up),
- ReceiveBPS: StringToBps(option.Down),
- SalamanderPassword: salamanderPassword,
- Password: option.Password,
- TLSConfig: tlsConfig,
- UDPDisabled: false,
- CWND: option.CWND,
- UdpMTU: option.UdpMTU,
- ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
- return resolveUDPAddrWithPrefer(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
- },
- }
- var ranges utils.IntRanges[uint16]
- var serverAddress []string
- if option.Ports != "" {
- ranges, err = utils.NewUnsignedRanges[uint16](option.Ports)
- if err != nil {
- return nil, err
- }
- ranges.Range(func(port uint16) bool {
- serverAddress = append(serverAddress, net.JoinHostPort(option.Server, strconv.Itoa(int(port))))
- return true
- })
- if len(serverAddress) > 0 {
- clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
- return resolveUDPAddrWithPrefer(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
- }
- if option.HopInterval == 0 {
- option.HopInterval = defaultHopInterval
- } else if option.HopInterval < minHopInterval {
- option.HopInterval = minHopInterval
- }
- clientOptions.HopInterval = time.Duration(option.HopInterval) * time.Second
- }
- }
- if option.Port == 0 && len(serverAddress) == 0 {
- return nil, errors.New("invalid port")
- }
- client, err := hysteria2.NewClient(clientOptions)
- if err != nil {
- return nil, err
- }
- outbound := &Hysteria2{
- Base: &Base{
- name: option.Name,
- addr: addr,
- tp: C.Hysteria2,
- udp: true,
- iface: option.Interface,
- rmark: option.RoutingMark,
- prefer: C.NewDNSPrefer(option.IPVersion),
- },
- option: &option,
- client: client,
- dialer: singDialer,
- }
- runtime.SetFinalizer(outbound, closeHysteria2)
- return outbound, nil
- }
|