hysteria.go 8.5 KB

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