123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- package outbound
- import (
- "context"
- "crypto/tls"
- "encoding/base64"
- "fmt"
- "net"
- "net/netip"
- "strconv"
- "time"
- "github.com/metacubex/quic-go"
- "github.com/metacubex/quic-go/congestion"
- M "github.com/sagernet/sing/common/metadata"
- "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"
- hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion"
- "github.com/metacubex/mihomo/transport/hysteria/core"
- "github.com/metacubex/mihomo/transport/hysteria/obfs"
- "github.com/metacubex/mihomo/transport/hysteria/pmtud_fix"
- "github.com/metacubex/mihomo/transport/hysteria/transport"
- "github.com/metacubex/mihomo/transport/hysteria/utils"
- )
- const (
- mbpsToBps = 125000
- DefaultStreamReceiveWindow = 15728640 // 15 MB/s
- DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
- DefaultALPN = "hysteria"
- DefaultProtocol = "udp"
- DefaultHopInterval = 10
- )
- type Hysteria struct {
- *Base
- option *HysteriaOption
- client *core.Client
- }
- func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
- tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...))
- if err != nil {
- return nil, err
- }
- return NewConn(tcpConn, h), nil
- }
- func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
- udpConn, err := h.client.DialUDP(h.genHdc(ctx, opts...))
- if err != nil {
- return nil, err
- }
- return newPacketConn(&hyPacketConn{udpConn}, h), nil
- }
- func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
- return &hyDialerWithContext{
- ctx: context.Background(),
- hyDialer: func(network string) (net.PacketConn, error) {
- var err error
- var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
- if len(h.option.DialerProxy) > 0 {
- cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer)
- if err != nil {
- return nil, err
- }
- }
- rAddrPort, _ := netip.ParseAddrPort(h.Addr())
- return cDialer.ListenPacket(ctx, network, "", rAddrPort)
- },
- remoteAddr: func(addr string) (net.Addr, error) {
- return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
- },
- }
- }
- type HysteriaOption struct {
- BasicOption
- Name string `proxy:"name"`
- Server string `proxy:"server"`
- Port int `proxy:"port,omitempty"`
- Ports string `proxy:"ports,omitempty"`
- Protocol string `proxy:"protocol,omitempty"`
- ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
- Up string `proxy:"up"`
- UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
- Down string `proxy:"down"`
- DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
- Auth string `proxy:"auth,omitempty"`
- AuthString string `proxy:"auth-str,omitempty"`
- Obfs string `proxy:"obfs,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"`
- ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
- ReceiveWindow int `proxy:"recv-window,omitempty"`
- DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
- FastOpen bool `proxy:"fast-open,omitempty"`
- HopInterval int `proxy:"hop-interval,omitempty"`
- }
- func (c *HysteriaOption) Speed() (uint64, uint64, error) {
- var up, down uint64
- up = StringToBps(c.Up)
- if up == 0 {
- return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
- }
- down = StringToBps(c.Down)
- if down == 0 {
- return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
- }
- return up, down, nil
- }
- func NewHysteria(option HysteriaOption) (*Hysteria, error) {
- clientTransport := &transport.ClientTransport{
- Dialer: &net.Dialer{
- Timeout: 8 * time.Second,
- },
- }
- addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
- ports := option.Ports
- 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
- } else {
- tlsConfig.NextProtos = []string{DefaultALPN}
- }
- quicConfig := &quic.Config{
- InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
- MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
- InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
- MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
- KeepAlivePeriod: 10 * time.Second,
- DisablePathMTUDiscovery: option.DisableMTUDiscovery,
- EnableDatagrams: true,
- }
- if option.ObfsProtocol != "" {
- option.Protocol = option.ObfsProtocol
- }
- if option.Protocol == "" {
- option.Protocol = DefaultProtocol
- }
- if option.HopInterval == 0 {
- option.HopInterval = DefaultHopInterval
- }
- hopInterval := time.Duration(int64(option.HopInterval)) * time.Second
- if option.ReceiveWindow == 0 {
- quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow / 10
- quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow
- }
- if option.ReceiveWindow == 0 {
- quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow / 10
- quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow
- }
- if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery {
- log.Infoln("hysteria: Path MTU Discovery is not yet supported on this platform")
- }
- var auth = []byte(option.AuthString)
- if option.Auth != "" {
- auth, err = base64.StdEncoding.DecodeString(option.Auth)
- if err != nil {
- return nil, err
- }
- }
- var obfuscator obfs.Obfuscator
- if len(option.Obfs) > 0 {
- obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs))
- }
- up, down, err := option.Speed()
- if err != nil {
- return nil, err
- }
- if option.UpSpeed != 0 {
- up = uint64(option.UpSpeed * mbpsToBps)
- }
- if option.DownSpeed != 0 {
- down = uint64(option.DownSpeed * mbpsToBps)
- }
- client, err := core.NewClient(
- addr, ports, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
- return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
- }, obfuscator, hopInterval, option.FastOpen,
- )
- if err != nil {
- return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
- }
- return &Hysteria{
- Base: &Base{
- name: option.Name,
- addr: addr,
- tp: C.Hysteria,
- udp: true,
- tfo: option.FastOpen,
- iface: option.Interface,
- rmark: option.RoutingMark,
- prefer: C.NewDNSPrefer(option.IPVersion),
- },
- option: &option,
- client: client,
- }, nil
- }
- type hyPacketConn struct {
- core.UDPConn
- }
- func (c *hyPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
- b, addrStr, err := c.UDPConn.ReadFrom()
- if err != nil {
- return
- }
- n = copy(p, b)
- addr = M.ParseSocksaddr(addrStr).UDPAddr()
- return
- }
- func (c *hyPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
- b, addrStr, err := c.UDPConn.ReadFrom()
- if err != nil {
- return
- }
- data = b
- addr = M.ParseSocksaddr(addrStr).UDPAddr()
- return
- }
- func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
- err = c.UDPConn.WriteTo(p, M.SocksaddrFromNet(addr).String())
- if err != nil {
- return
- }
- n = len(p)
- return
- }
- type hyDialerWithContext struct {
- hyDialer func(network string) (net.PacketConn, error)
- ctx context.Context
- remoteAddr func(host string) (net.Addr, error)
- }
- func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, error) {
- network := "udp"
- if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
- network = dialer.ParseNetwork(network, addrPort.Addr())
- }
- return h.hyDialer(network)
- }
- func (h *hyDialerWithContext) Context() context.Context {
- return h.ctx
- }
- func (h *hyDialerWithContext) RemoteAddr(host string) (net.Addr, error) {
- return h.remoteAddr(host)
- }
|