Clash.Meta/adapter/outbound/hysteria2.go

206 lines
5.9 KiB
Go
Raw Normal View History

2023-09-21 10:28:28 +08:00
package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"runtime"
"strconv"
"time"
2023-09-21 10:28:28 +08:00
2023-11-03 21:01:45 +08:00
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils"
2023-11-03 21:01:45 +08:00
"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"
2023-11-03 21:01:45 +08:00
tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
2023-09-21 10:28:28 +08:00
"github.com/metacubex/sing-quic/hysteria2"
M "github.com/sagernet/sing/common/metadata"
2024-03-10 23:49:54 +08:00
"github.com/zhangyunhao116/fastrand"
2023-09-21 10:28:28 +08:00
)
func init() {
hysteria2.SetCongestionController = tuicCommon.SetCongestionController
}
const minHopInterval = 5
const defaultHopInterval = 30
2023-09-21 10:28:28 +08:00
type Hysteria2 struct {
*Base
option *Hysteria2Option
client *hysteria2.Client
2023-09-25 09:10:43 +08:00
dialer proxydialer.SingDialer
2023-09-21 10:28:28 +08:00
}
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"`
2023-09-21 10:28:28 +08:00
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"`
2023-09-21 16:41:31 +08:00
CWND int `proxy:"cwnd,omitempty"`
UdpMTU int `proxy:"udp-mtu,omitempty"`
2023-09-21 10:28:28 +08:00
}
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := h.Base.DialOptions(opts...)
2023-09-25 09:10:43 +08:00
h.dialer.SetDialer(dialer.NewDialer(options...))
c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
2023-09-21 10:28:28 +08:00
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...)
2023-09-25 09:10:43 +08:00
h.dialer.SetDialer(dialer.NewDialer(options...))
2023-09-21 10:28:28 +08:00
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
2023-09-22 14:45:34 +08:00
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
if err != nil {
return nil, err
2023-09-21 10:28:28 +08:00
}
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
}
2023-09-25 09:10:43 +08:00
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
2023-09-21 10:28:28 +08:00
clientOptions := hysteria2.ClientOptions{
Context: context.TODO(),
Dialer: singDialer,
Logger: log.SingLogger,
2023-09-21 14:52:26 +08:00
SendBPS: StringToBps(option.Up),
ReceiveBPS: StringToBps(option.Down),
2023-09-21 10:28:28 +08:00
SalamanderPassword: salamanderPassword,
Password: option.Password,
TLSConfig: tlsConfig,
UDPDisabled: false,
2023-09-21 16:41:31 +08:00
CWND: option.CWND,
UdpMTU: option.UdpMTU,
2024-03-10 23:49:54 +08:00
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
},
2023-09-21 10:28:28 +08:00
}
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 {
2024-03-10 23:49:54 +08:00
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", serverAddress[fastrand.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")
}
2023-09-21 10:28:28 +08:00
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
}