mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2024-11-14 05:11:17 +08:00
211 lines
5.9 KiB
Go
211 lines
5.9 KiB
Go
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
|
|
|
|
closeCh chan struct{} // for test
|
|
}
|
|
|
|
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"))
|
|
}
|
|
if h.closeCh != nil {
|
|
close(h.closeCh)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|