github.com/metacubex/mihomo@v1.18.5/adapter/outbound/hysteria.go (about) 1 package outbound 2 3 import ( 4 "context" 5 "crypto/tls" 6 "encoding/base64" 7 "fmt" 8 "net" 9 "net/netip" 10 "strconv" 11 "time" 12 13 "github.com/metacubex/quic-go" 14 "github.com/metacubex/quic-go/congestion" 15 M "github.com/sagernet/sing/common/metadata" 16 17 "github.com/metacubex/mihomo/component/ca" 18 "github.com/metacubex/mihomo/component/dialer" 19 "github.com/metacubex/mihomo/component/proxydialer" 20 C "github.com/metacubex/mihomo/constant" 21 "github.com/metacubex/mihomo/log" 22 hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion" 23 "github.com/metacubex/mihomo/transport/hysteria/core" 24 "github.com/metacubex/mihomo/transport/hysteria/obfs" 25 "github.com/metacubex/mihomo/transport/hysteria/pmtud_fix" 26 "github.com/metacubex/mihomo/transport/hysteria/transport" 27 "github.com/metacubex/mihomo/transport/hysteria/utils" 28 ) 29 30 const ( 31 mbpsToBps = 125000 32 33 DefaultStreamReceiveWindow = 15728640 // 15 MB/s 34 DefaultConnectionReceiveWindow = 67108864 // 64 MB/s 35 36 DefaultALPN = "hysteria" 37 DefaultProtocol = "udp" 38 DefaultHopInterval = 10 39 ) 40 41 type Hysteria struct { 42 *Base 43 44 option *HysteriaOption 45 client *core.Client 46 } 47 48 func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { 49 tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...)) 50 if err != nil { 51 return nil, err 52 } 53 54 return NewConn(tcpConn, h), nil 55 } 56 57 func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 58 udpConn, err := h.client.DialUDP(h.genHdc(ctx, opts...)) 59 if err != nil { 60 return nil, err 61 } 62 return newPacketConn(&hyPacketConn{udpConn}, h), nil 63 } 64 65 func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer { 66 return &hyDialerWithContext{ 67 ctx: context.Background(), 68 hyDialer: func(network string) (net.PacketConn, error) { 69 var err error 70 var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...) 71 if len(h.option.DialerProxy) > 0 { 72 cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer) 73 if err != nil { 74 return nil, err 75 } 76 } 77 rAddrPort, _ := netip.ParseAddrPort(h.Addr()) 78 return cDialer.ListenPacket(ctx, network, "", rAddrPort) 79 }, 80 remoteAddr: func(addr string) (net.Addr, error) { 81 return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer) 82 }, 83 } 84 } 85 86 type HysteriaOption struct { 87 BasicOption 88 Name string `proxy:"name"` 89 Server string `proxy:"server"` 90 Port int `proxy:"port,omitempty"` 91 Ports string `proxy:"ports,omitempty"` 92 Protocol string `proxy:"protocol,omitempty"` 93 ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash 94 Up string `proxy:"up"` 95 UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash 96 Down string `proxy:"down"` 97 DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash 98 Auth string `proxy:"auth,omitempty"` 99 AuthString string `proxy:"auth-str,omitempty"` 100 Obfs string `proxy:"obfs,omitempty"` 101 SNI string `proxy:"sni,omitempty"` 102 SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 103 Fingerprint string `proxy:"fingerprint,omitempty"` 104 ALPN []string `proxy:"alpn,omitempty"` 105 CustomCA string `proxy:"ca,omitempty"` 106 CustomCAString string `proxy:"ca-str,omitempty"` 107 ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"` 108 ReceiveWindow int `proxy:"recv-window,omitempty"` 109 DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` 110 FastOpen bool `proxy:"fast-open,omitempty"` 111 HopInterval int `proxy:"hop-interval,omitempty"` 112 } 113 114 func (c *HysteriaOption) Speed() (uint64, uint64, error) { 115 var up, down uint64 116 up = StringToBps(c.Up) 117 if up == 0 { 118 return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up) 119 } 120 121 down = StringToBps(c.Down) 122 if down == 0 { 123 return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down) 124 } 125 126 return up, down, nil 127 } 128 129 func NewHysteria(option HysteriaOption) (*Hysteria, error) { 130 clientTransport := &transport.ClientTransport{ 131 Dialer: &net.Dialer{ 132 Timeout: 8 * time.Second, 133 }, 134 } 135 addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) 136 ports := option.Ports 137 138 serverName := option.Server 139 if option.SNI != "" { 140 serverName = option.SNI 141 } 142 143 tlsConfig := &tls.Config{ 144 ServerName: serverName, 145 InsecureSkipVerify: option.SkipCertVerify, 146 MinVersion: tls.VersionTLS13, 147 } 148 149 var err error 150 tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString) 151 if err != nil { 152 return nil, err 153 } 154 155 if len(option.ALPN) > 0 { 156 tlsConfig.NextProtos = option.ALPN 157 } else { 158 tlsConfig.NextProtos = []string{DefaultALPN} 159 } 160 quicConfig := &quic.Config{ 161 InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn), 162 MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn), 163 InitialConnectionReceiveWindow: uint64(option.ReceiveWindow), 164 MaxConnectionReceiveWindow: uint64(option.ReceiveWindow), 165 KeepAlivePeriod: 10 * time.Second, 166 DisablePathMTUDiscovery: option.DisableMTUDiscovery, 167 EnableDatagrams: true, 168 } 169 if option.ObfsProtocol != "" { 170 option.Protocol = option.ObfsProtocol 171 } 172 if option.Protocol == "" { 173 option.Protocol = DefaultProtocol 174 } 175 if option.HopInterval == 0 { 176 option.HopInterval = DefaultHopInterval 177 } 178 hopInterval := time.Duration(int64(option.HopInterval)) * time.Second 179 if option.ReceiveWindow == 0 { 180 quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow / 10 181 quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow 182 } 183 if option.ReceiveWindow == 0 { 184 quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow / 10 185 quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow 186 } 187 if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery { 188 log.Infoln("hysteria: Path MTU Discovery is not yet supported on this platform") 189 } 190 191 var auth = []byte(option.AuthString) 192 if option.Auth != "" { 193 auth, err = base64.StdEncoding.DecodeString(option.Auth) 194 if err != nil { 195 return nil, err 196 } 197 } 198 var obfuscator obfs.Obfuscator 199 if len(option.Obfs) > 0 { 200 obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs)) 201 } 202 203 up, down, err := option.Speed() 204 if err != nil { 205 return nil, err 206 } 207 if option.UpSpeed != 0 { 208 up = uint64(option.UpSpeed * mbpsToBps) 209 } 210 if option.DownSpeed != 0 { 211 down = uint64(option.DownSpeed * mbpsToBps) 212 } 213 client, err := core.NewClient( 214 addr, ports, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl { 215 return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) 216 }, obfuscator, hopInterval, option.FastOpen, 217 ) 218 if err != nil { 219 return nil, fmt.Errorf("hysteria %s create error: %w", addr, err) 220 } 221 return &Hysteria{ 222 Base: &Base{ 223 name: option.Name, 224 addr: addr, 225 tp: C.Hysteria, 226 udp: true, 227 tfo: option.FastOpen, 228 iface: option.Interface, 229 rmark: option.RoutingMark, 230 prefer: C.NewDNSPrefer(option.IPVersion), 231 }, 232 option: &option, 233 client: client, 234 }, nil 235 } 236 237 type hyPacketConn struct { 238 core.UDPConn 239 } 240 241 func (c *hyPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 242 b, addrStr, err := c.UDPConn.ReadFrom() 243 if err != nil { 244 return 245 } 246 n = copy(p, b) 247 addr = M.ParseSocksaddr(addrStr).UDPAddr() 248 return 249 } 250 251 func (c *hyPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) { 252 b, addrStr, err := c.UDPConn.ReadFrom() 253 if err != nil { 254 return 255 } 256 data = b 257 addr = M.ParseSocksaddr(addrStr).UDPAddr() 258 return 259 } 260 261 func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 262 err = c.UDPConn.WriteTo(p, M.SocksaddrFromNet(addr).String()) 263 if err != nil { 264 return 265 } 266 n = len(p) 267 return 268 } 269 270 type hyDialerWithContext struct { 271 hyDialer func(network string) (net.PacketConn, error) 272 ctx context.Context 273 remoteAddr func(host string) (net.Addr, error) 274 } 275 276 func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, error) { 277 network := "udp" 278 if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil { 279 network = dialer.ParseNetwork(network, addrPort.Addr()) 280 } 281 return h.hyDialer(network) 282 } 283 284 func (h *hyDialerWithContext) Context() context.Context { 285 return h.ctx 286 } 287 288 func (h *hyDialerWithContext) RemoteAddr(host string) (net.Addr, error) { 289 return h.remoteAddr(host) 290 }